KVC: Key-value Coding 一个非正式的Protocol,主要实现在NSObject以及容器的NSKeyValueCoding分类中
一、基本使用
设置keyPath和key的区别:keyPath可以嵌套多层取值,而key只能直接取值
JRTestKvcObj *t1 = [JRTestKvcObj new];
t1.child = [JRTestKvcObj new];
[t1 setValue:@"stone" forKey:@"name"];
NSLog(@"%@",[t1 valueForKey:@"name"]);
[t1 setValue:@"jirun" forKeyPath:@"name"];
NSLog(@"%@",[t1 valueForKeyPath:@"name"]);
[t1 setValue:@"tom" forKeyPath:@"child.name"];
NSLog(@"%@",[t1 valueForKeyPath:@"child.name"]);
@try {
[t1 setValue:@"jimmy" forKey:@"child.name"];
NSLog(@"%@",[t1 valueForKey:@"child.name"]);
} @catch (NSException * e) {
NSLog(@"%@",e);
}
二、简单的字典模型互转
NSDictionary *dict = @{@"name":@"jirun",@"age":@(1)};
JRTestKvcObj *t2 = [JRTestKvcObj new];
[t2 setValuesForKeysWithDictionary:dict];
NSLog(@"%@",[t2 dictionaryWithValuesForKeys:@[@"name",@"age"]]);
三、容器的KVC
字典取值三种方式的区别:
- setValue:forKeyPath:和valueForKeyPath:可以嵌套层级
- setObject:forKey:和ObjectForKey:中的key可以不为NSString
- setObject:forKey:中的object不能为空,但是另外两者可以
NSMutableDictionary *mDict1 = [NSMutableDictionary dictionary];
[mDict1 setValue:@"value1" forKey:@"key1"];
[mDict1 setObject:@"value2" forKey:@"key2"];
[mDict1 setValue:@"value3" forKeyPath:@"key3"];
NSLog(@"%@",[mDict1 valueForKey:@"key1"]);
NSLog(@"%@",[mDict1 objectForKey:@"key2"]);
NSLog(@"%@",[mDict1 valueForKeyPath:@"key3"]);
[mDict1 setValue:[NSMutableDictionary dictionary] forKey:@"key1"];
[mDict1 setValue:@"a" forKeyPath:@"key1.key2"];
NSLog(@"%@", [mDict1 valueForKeyPath:@"key1.key2"]);
[mDict1 setObject:@"value3" forKey:@(1)];
NSLog(@"%@",[mDict1 objectForKey:@(1)]);
@try {
id object;
[mDict1 setObject:object forKey:@"key"];
} @catch (NSException *e) {
NSLog(@"%@",e);
}
四、KVC查询步骤
setValue:forKey:查找步骤:
- 查找-set<Key>:方法
- 检查+(BOOL)accessInstanceVariablesDirectly是否返回YES,返回YES则继续寻找,否则调用setValue:forUndefinedKey:,默认返回YES
- 查找变量_<key> _is<Key>
- 查找变量<key> is<Key>
- 如果还是没找到,则调用setValue:forUndefinedKey:(默认抛出异常)
valueForKey:查找步骤:
- 查找-get<Key>,<key>,is<Key>方法
- 查找countOf<Key>,objectIn<Key>AtIndex或<key>AtIndexes,如果同时存在第一个方法以及二三方法中的一个,则返回一个NSKeyValueArray对象
- 如果同时实现countOf<Key>,enumeratorOf<Key>,memberOf<Key>三个方法,则返回一个NSSet对象
- 检查+(BOOL)accessInstanceVariablesDirectly是否返回YES,返回YES则继续寻找,否则调用valueForUndefinedKey:
- 查找变量_<key>,_is<Key>,<key>,is<Key>
6.如果还没找到调用valueForUndefineKey:(默认抛出异常)
mutableArrayValueForKey:查找步骤:
- 查找insertObject:in<Key>AtIndex: , removeObjectFrom<Key>AtIndex: 或者 insert<Key>AtIndexes , remove<Key>AtIndexes,只要存在一组插入和获取的方法则直接返回代理集合对象,调用代理集合的插入和删除方法内部会调用相对应的对象方法
- 搜索set<Key>:,如果存在则返回代理集合,修改代理集合都会自动重新对原属性赋值(触发KVO)
- + (BOOL)accessInstanceVariablesDirectly是否返回YES,返回YES则继续寻找,否则调用valueForUndefinedKey:(默认抛出异常)
- 搜索变量_<key>,<key>,发送给代理集合的修改方法都会直接发送给该成员对象
- 还没有找到则直接返回一个代理集合(此时没有调用valueForUndefinedKey:),但如果调用代理集合的方法都会触发valueForUndefinedKey:(默认抛出异常)
JRTestKvcObj * testObj = [JRTestKvcObj new];
testObj.arr = [NSArray array];
NSArray *originArr = testObj.arr;
NSArray *arr = [testObj valueForKey:@"arr"];
NSLog(@"%d",arr == testObj.arr);//1
NSMutableArray *delegateArr = [testObj mutableArrayValueForKey:@"arr"];
[delegateArr addObject:@"1"];
NSLog(@"%ld - %ld",testObj.arr.count, delegateArr.count); // 1 - 1
NSLog(@"%d",testObj.arr == delegateArr); //0
NSLog(@"%d",testObj.arr == originArr); //0
五、KVC的异常防护
个人推荐使用第一种和第三种
- 通过重写setValue:forUndefinedKey:、setNilValueForKey:、valueForUndefineKey:,这种方法只能运用在自己类的对象中
- swizzling NSObject类或者非自定义类的上述三个方法,可以运用在所有的对象中,但是影响未知
- 通过trycatch捕获异常(最好类扩展方法,内部异常转error)
- 方法查询,通过查询对象是否存在访问器和变量,然后再调用KVC
JRTestKvcObj *obj = [JRTestKvcObj new];
// 触发NSUndefinedKeyException异常,通过重写setValue:forUndefinedKey:避免异常
[obj setValue:@"value" forKey:@"unDefineKey"];
// 触发NSUndefinedKeyException异常,通过重写valueForUndefineKey:避免异常
id value = [obj valueForKey:@"unDefineKey"];
// 如可以封装成NSNumber或者NSValue的基本数据类型,如果设置的时候value为空会触发异常
// 可以通过重写setNilValueForKey:避免异常
[obj setValue:nil forKey:@"num"];
/*
validateValue:forKey:error:可以验证value的合法性,需要手动调用
默认该方法会调用-validate<Key>:error方法,如果该方法没有实现则直接返回YES。
也就是说默认的validateValue:forKey:error:返回了YES
*/
NSError *e;
id age = @"15";
if ([obj validateValue:&age forKey:@"age" error:&e]) {
[obj setValue:age forKey:@"age"];
}
六、KVC使用扩展
@min,@max,@avg,@count,@sum
@unionOfObjects,@"distinctUnionOfObjects",@unionOfArrays,distinctUnionOfArrays等
- unionOfObjects和unionOfArrays获取并集
- distinctUnionOfObjects和distinctUnionOfArrays会对并集做去重操作
- unionOfArrays和distinctUnionOfArrays用于处理数组的元素仍是数组的情况
- 去重后的数组顺序不定
- 如果元素为空则结果集中会存在NSNull
JRTestKvcObj *obj1 = [JRTestKvcObj new];
obj1.name = @"name1";
obj1.length = @"1";
JRTestKvcObj *obj2 = [JRTestKvcObj new];
obj2.name = @"name2";
obj2.length = @"2";
JRTestKvcObj *obj3 = [JRTestKvcObj new];
obj3.name = @"name2";
obj3.length = @"3";
NSArray *arr1 = @[obj1,obj2,obj3];
NSArray *lengthArr = [arr1 valueForKeyPath:@"length"];
NSArray *nameArr = [arr1 valueForKeyPath:@"name.uppercaseString"];
NSArray *distictNameArr = [arr1 valueForKeyPath:@"@distinctUnionOfObjects.name"];
NSNumber *min = [arr1 valueForKeyPath:@"length.@min.doubleValue"];
NSNumber *max = [arr1 valueForKeyPath:@"length.@max.doubleValue"];
NSNumber *average = [arr1 valueForKeyPath:@"length.@avg.doubleValue"];
NSNumber *sum = [arr1 valueForKeyPath:@"length.@sum.doubleValue"];
NSNumber *count = [arr1 valueForKey:@"@count"];
NSArray *arr2 = @[@[obj1,obj2],@[obj1,obj2,obj3]];
NSArray *nameArr2 = [arr2 valueForKeyPath:@"name"]; // [[nam1,name2],[name1,name2,name2]]
NSArray *unionArr2 = [arr2 valueForKeyPath:@"@unionOfArrays.name"]; //[name1,name2,name1,name2,name2]
NSArray *distictArr2 = [arr2 valueForKeyPath:@"@distinctUnionOfArrays.name"]; //[name2,name1]
七、KVC的优缺点(个人观点)
优点:
- 功能强大,可以访问私有域
- 解耦的一种途径
- 减少代码量
缺点:
- 没有条件防护或不恰当的使用容易引发崩溃
- 缺少编译期检查,把异常推迟到运行期间
建议:
- 不要大量使用KVC
- KVC的使用尽量在模块内部
- 做好异常防护或参数合法性验证