KVC
全称是Key Value Coding
,在NSKeyValueCoding.h
非正式协议文件中,声明了KVC
能使用的方法,KVC
提供一种通过字符串来访问类中的属性和成员变量的方法。
-
NSKeyValueCoding协议中的方法
NSKeyValueCoding.gif
从NSKeyValueCoding协议方法说去
先来看看几个宏
当调用KVC时key值为空时,就会抛出这个异常。
FOUNDATION_EXPORT NSExceptionName const NSUndefinedKeyException;
// NSKeyValueCoding中的运算符
NSKeyValueOperator const NSAverageKeyValueOperator; // 求平均值
NSKeyValueOperator const NSCountKeyValueOperator; // 统计总数
NSKeyValueOperator const NSDistinctUnionOfArraysKeyValueOperator; // 获取嵌套数组中不同的值
NSKeyValueOperator const NSDistinctUnionOfObjectsKeyValueOperator; // 获取不同的值
NSKeyValueOperator const NSDistinctUnionOfSetsKeyValueOperator; // 获取嵌套集合中不同的值
NSKeyValueOperator const NSMaximumKeyValueOperator; // 获取最大值
NSKeyValueOperator const NSMinimumKeyValueOperator; // 获取最小值
NSKeyValueOperator const NSSumKeyValueOperator; // 求和
NSKeyValueOperator const NSUnionOfArraysKeyValueOperator; // 获取嵌套数组中的值,不去重
NSKeyValueOperator const NSUnionOfObjectsKeyValueOperator; // 获取所有的值,不去重
NSKeyValueOperator const NSUnionOfSetsKeyValueOperator; // 获取嵌套集合中的值,不去重
这些运算符能帮助我们快速的运算集合中的操作。在官方文档中教了我们怎么使用这些操作符。
NSArray *array = @[@"1",@"1",@"1",@"2",@"2",@"3",@"3",@"4",@"5",@"5"];
NSArray *array1 = @[@"3",@"4",@"5",@"5",@"6",@"6",@"7",@"8",@"8"];
NSArray *unionArray = @[array,array1];
- 获取数组的个数,这个一般没有,因为数组就要count属性。
NSString *count = [array valueForKeyPath:@"@count"];
NSLog(@"数组个数 = %@",count);
2018-06-19 15:50:20.568834+0800 KVC和NSKeyValueCoding[10417:285376] 数组个数 = 10
- 获取数组的平均值
NSString *avg = [array valueForKeyPath:@"@avg.self"];
NSLog(@"数组平均值 = %@",avg);
2018-06-19 15:50:20.569342+0800 KVC和NSKeyValueCoding[10417:285376] 数组平均值 = 2.7
- 获取去重后的数组。如果有一道题目是让我们从一万个重复的数据中找出不同的数,就可以用这个方法。当然也可以数组转NSSet去重,再转NSArray排序。
NSArray *distinct = [array valueForKeyPath:@"@distinctUnionOfObjects.self"];
NSArray *distinctUnionOfObjects = [distinct sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
return [obj1 compare:obj2];
}]; //数组正序
NSLog(@"去重后的数组 = %@",distinctUnionOfObjects);
2018-06-19 15:50:20.569594+0800 KVC和NSKeyValueCoding[10417:285376] 去重后的数组 = (
1,
2,
3,
4,
5
)
- 数组数字最大值
NSString *max = [array valueForKeyPath:@"@max.self"];
NSLog(@"数组数字最大值 = %@",max);
2018-06-19 15:50:20.569777+0800 KVC和NSKeyValueCoding[10417:285376] 数组数字最大值 = 5
- 数组数字最小值
NSString *min = [array valueForKeyPath:@"@min.self"];
NSLog(@"数组数字最小值 = %@",min);
2018-06-19 15:50:20.569928+0800 KVC和NSKeyValueCoding[10417:285376] 数组数字最小值 = 1
- 获取数组数字总和
NSString *sum = [array valueForKeyPath:@"@sum.self"];
NSLog(@"总和 = %@", sum);
2018-06-19 15:50:20.570135+0800 KVC和NSKeyValueCoding[10417:285376] 总和 = 27
- 不去重的数组,这个在开发中也没用,因为就算数组本身。
NSArray *unionOfObjects = [array valueForKeyPath:@"@unionOfObjects.self"];
NSLog(@"不去重的数组 = %@", unionOfObjects);
2018-06-19 15:50:20.570313+0800 KVC和NSKeyValueCoding[10417:285376] 不去重的数组 = (
1,
1,
1,
2,
2,
3,
3,
4,
5,
5
)
- 多个数组去重。去重后变一个数组。
NSArray *distinctUnionOfArrays = [unionArray valueForKeyPath:@"@distinctUnionOfArrays.self"];
NSLog(@"多个数组去重后 = %@",distinctUnionOfArrays);
2018-06-19 15:50:20.570545+0800 KVC和NSKeyValueCoding[10417:285376] 多个数组去重后 = (
2,
3,
4,
5,
6,
7,
8,
1
)
- 多个数组不去重,这个在开发中被可变数组的addObjectsFromArray方法替代.
NSArray *unionOfArrays = [unionArray valueForKeyPath:@"@unionOfArrays.self"];
NSLog(@"多个数组不去重 = %@",unionOfArrays);
2018-06-19 15:50:20.570716+0800 KVC和NSKeyValueCoding[10417:285376] 多个数组不去重 = (
1,
1,
1,
2,
2,
3,
3,
4,
5,
5,
3,
4,
5,
5,
6,
6,
7,
8,
8
)
赋值和取值操作
- 创建一个 Animal 自定义对象
#import <Foundation/Foundation.h>
@class Food;
/**
对象
*/
@interface Animal : NSObject
// 动物姓名
@property (nonatomic, copy) NSString *name;
// 动物年龄
@property (nonatomic, assign) NSInteger age;
// 食物
@property (nonatomic, strong) Food *food;
@end
@interface Food : NSObject
// 水果
@property (nonatomic, copy) NSString *fruit;
// 肉
@property (nonatomic, copy) NSString *meat;
@end
#import "Animal.h"
@implementation Animal
- (Food *)food {
if (!_food) {
_food = [[Food alloc] init];
}
return _food;
}
@end
@implementation Food
@end
- 使用
- setValue: forKey:
赋值,valueForKey:
方法取值。直接通过Animal中的属性名来作为key赋值取值。
[_animal setValue:@"小妹" forKey:@"name"];
NSLog(@"name = %@",[_animal valueForKey:@"name"]);
2018-06-19 17:13:32.029108+0800 KVC和NSKeyValueCoding[11681:325413] name = 小妹
- 如果想对
Animal
对象中的Food
对象的fruit
属性赋值,这种多级访问设置或获取value
就要用到keyPath
来存取。
[_animal setValue:@"苹果" forKeyPath:@"food.fruit"];
NSLog(@"fruit = %@",[_animal valueForKeyPath:@"food.fruit"]);
2018-06-19 17:34:33.650983+0800 KVC和NSKeyValueCoding[11746:338536] fruit = 苹果
如果用- setValue: forKey:
存取,则会造成crash
[_animal setValue:@"苹果" forKey:@"food.fruit"];
NSLog(@"fruit = %@",[_animal valueForKey:@"food.fruit"]);
Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<Animal 0x60000003cdc0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key food.fruit.'
多值的赋值和取值
- 使用的方法
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
- 例子
[_animal setValuesForKeysWithDictionary:@{@"name":@"小马哥" , @"age" : @(25)}];
NSLog(@"name = %@",[_animal valueForKey:@"name"]);
NSLog(@"age = %li",_animal.age);
NSLog(@"赋值后为 %@",[_animal dictionaryWithValuesForKeys:@[@"name",@"age"]]);
- 方法输出
2018-06-20 09:19:00.728542+0800 KVC和NSKeyValueCoding[12597:503862] name = 小马哥
2018-06-20 09:19:00.728667+0800 KVC和NSKeyValueCoding[12597:503862] age = 25
2018-06-20 09:19:00.728941+0800 KVC和NSKeyValueCoding[12597:503862] 赋值后为 {
age = 25;
name = "\U5c0f\U9a6c\U54e5";
}
- 项目中,我们字典转模型通常是
// 初始化赋值
- (instancetype)initWithDic:(NSDictionary *)dic {
if (self = [super init]) {
self.age = [dic[@"age"] integerValue];
self.name = dic[@"name"];
}
return self;
}
但是当我们知道了多值操作的方法,并且model
的属性和字典的key
相同,我们可以使用下面方法,减少赋值的多余代码。
// 初始化赋值
- (instancetype)initWithDic:(NSDictionary *)dic {
if (self = [super init]) {
[self setValuesForKeysWithDictionary:dic];
}
return self;
}
// 重写 setValuesForKeysWithDictionary方法 去除 nil和null
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *,id> *)keyedValues {
for (id key in keyedValues.allKeys) {
if (![keyedValues valueForKey:key]) {
[self setValue:@"" forKey:key];
} else if ([[keyedValues valueForKey:key] isEqual:[NSNull null]]) {
[self setValue:@"" forKey:key];
} else {
[self setValue:[keyedValues valueForKey:key] forKey:key];
}
}
}
集合getter
在对集合对象进行getter
操作时,可以调用一下方法通过key
或者keyPath
获取。如果再对容器类进行add
或remove
等操作时,就会触发KVO
的消息通知
- key
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
- (NSMutableOrderedSet *)mutableOrderedSetValueForKey:(NSString *)key API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
- (NSMutableSet *)mutableSetValueForKey:(NSString *)key;
- keypath
- (NSMutableArray *)mutableArrayValueForKeyPath:(NSString *)keyPath;
- (NSMutableOrderedSet *)mutableOrderedSetValueForKeyPath:(NSString *)keyPath API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
- (NSMutableSet *)mutableSetValueForKeyPath:(NSString *)keyPath;
属性验证
- 相对应的也有key和keyPath对应的两个方法。用来验证传入的value和key是否正确。
- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKeyPath:(NSString *)inKeyPath error:(out NSError **)outError;
// 验证码`key value` 是否满足需求
- (BOOL)validateValue:(inout id _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError * _Nullable __autoreleasing *)outError {
if ([inKey isEqualToString:@"age"]) {
if ([*ioValue integerValue] < 18) {
*outError = [NSError errorWithDomain:AnimalErrorDomain code:10001 userInfo:@{NSLocalizedDescriptionKey : @"小动物还未成年"}];
return NO;
}
}
return YES;
}
NSNumber *age = @(12);
NSError *error;
if ([_animal validateValue:&age forKey:@"age" error:&error]) {
NSLog(@"小动物成年了");
} else {
NSLog(@"error = %@",error);
}
2018-06-20 09:07:57.458669+0800 KVC和NSKeyValueCoding[12550:497482] error = Error Domain=AnimalErrorDomain Code=10001 "小动物还未成年" UserInfo={NSLocalizedDescription=小动物还未成年}
KVC的底层实现
为了方便阅读,将实例变量放在了.h文件中。
-
赋值流程
1 先调用属性的setter方法完成赋值。
2 如果没有发现setter方法,则检查+ (BOOL)accessInstanceVariablesDirectly
方法,默认返回YES
,这是在没有找到存取器的时候才调用的方法。返回为YES表示查找_<key>
、_is<Key>
、<key>
、is<Key>
找value
,如果有则赋值。
3 如果都没有找到,则会调用setValue:forUndefinedKey:
方法并抛出异常。 -
测试
.h文件
#import <Foundation/Foundation.h>
@class Food;
/**
对象
*/
@interface Animal : NSObject {
NSString *name;
NSString *_name;
NSString *_isName;
NSString *isName;
}
// 动物姓名
@property (nonatomic, copy) NSString *name;
@end
.m文件
#pragma mark --- KVC的底层实现测试
// 是否可以按照 _<key>、_is<Key>、<key>、is<Key> 方式查找
+ (BOOL)accessInstanceVariablesDirectly {
return NO;
}
// setter
- (void)setName:(NSString *)name {
NSLog(@"调用了setter方法");
_name = name;
}
// 重写setValue: forKey: 用来查看当前的key。具体是_name,_isName,name,isName.
- (void)setValue:(id)value forKey:(NSString *)key {
NSLog(@"%@", [NSString stringWithFormat:@"当前调用的key %@",key]);
[super setValue:value forKey:key];
}
// kvc底层实现验证
Animal *animal = [[Animal alloc] init];
[animal setValue:@"晓东" forKey:@"name"];
NSLog(@"name = %@",[animal valueForKey:@"name"]);
打印
2018-06-20 13:38:45.541011+0800 KVC和NSKeyValueCoding[14274:650523] 当前调用的key name
2018-06-20 13:38:45.541134+0800 KVC和NSKeyValueCoding[14274:650523] 调用了setter方法
2018-06-20 13:38:45.541251+0800 KVC和NSKeyValueCoding[14274:650523] name = 晓东
现在把属性name去掉,这样就不会默认生成存取器方法。因为accessInstanceVariablesDirectly为NO,所有会走setValue:(id)value forUndefinedKey:(NSString *)key
方法。我们如果不想让外界访问类的成员变量,则可以将accessInstanceVariablesDirectly属性赋值为NO。
@interface Animal : NSObject {
NSString *name;
NSString *_name;
NSString *_isName;
NSString *isName;
}
// 动物姓名
//@property (nonatomic, copy) NSString *name;
#pragma mark --- KVC的底层实现测试
// 是否可以按照 _<key>、_is<Key>、<key>、is<Key> 方式查找
+ (BOOL)accessInstanceVariablesDirectly {
return NO;
}
// setter
//- (void)setName:(NSString *)name {
// NSLog(@"调用了setter方法");
// _name = name;
//}
// 重写setValue: forKey: 用来查看当前的key。具体是_name,_isName,name,isName.
- (void)setValue:(id)value forKey:(NSString *)key {
NSLog(@"%@", [NSString stringWithFormat:@"当前调用的key %@",key]);
[super setValue:value forKey:key];
}
2018-06-20 13:49:54.816830+0800 KVC和NSKeyValueCoding[14428:661962] 当前调用的key name
2018-06-20 13:49:54.823548+0800 KVC和NSKeyValueCoding[14428:661962] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<Animal 0x604000277500> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key name.'
接下来把accessInstanceVariablesDirectly变成YES,因为存在_name,_isName,name,isName四个实例变量,所以会找到相应的key并将value赋值。依次注释操作
@interface Animal : NSObject {
NSString *name;
// NSString *_name;
// NSString *_isName;
// NSString *isName;
}
@interface Animal : NSObject {
// NSString *name;
NSString *_name;
// NSString *_isName;
// NSString *isName;
}
@interface Animal : NSObject {
// NSString *name;
// NSString *_name;
NSString *_isName;
// NSString *isName;
}
@interface Animal : NSObject {
// NSString *name;
// NSString *_name;
// NSString *_isName;
NSString *isName;
}
打印出来的都是
2018-06-20 13:55:08.241934+0800 KVC和NSKeyValueCoding[14468:666416] 当前调用的key name
2018-06-20 13:56:40.147144+0800 KVC和NSKeyValueCoding[14497:668042] name = 晓东
当实例变量_name,_isName,name,isName都不存在时并且setter方法不存在,
NSKeyValueCoding[14428:661962] 当前调用的key name
2018-06-20 13:49:54.823548+0800 KVC和NSKeyValueCoding[14428:661962] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<Animal 0x604000277500> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key name.'
错误处理
-
key
值的错误
上面一直报的一个异常setValue:forUndefinedKey:
,我们可以通过重写这个方法来停止报这个异常。
-(void)setValue:(id)value forUndefinedKey:(NSString *)key{
NSLog(@"赋值失败,该key不存在%@",key);
}
重写后打印。
2018-06-20 14:05:23.053180+0800 KVC和NSKeyValueCoding[14535:674635] 赋值失败,该key不存在name
网友评论