在日常发开中,我们可以通过断点调试、源码查看、LLDB调试来探索技术的原理,其实文档查看也是一种极其重要的手段。
KVC(Key-Value Coding)是我们日常开发中常见的一种技术,那么底层又是如何实现的呢?我们先看官方文档Key-Value Coding Programming Guide,当然你可以在文档首页搜索其他技术的文档Apple Documentation Archive
KVC简介
Key-value coding is a mechanism enabled by the NSKeyValueCoding informal protocol that objects adopt to provide indirect access to their properties.
可以在文档上看到:KVC是一种能够通过NSKeyValueCoding协议直接访问对象属性的机制。
getter方法- valueForKey:
- 1.寻找是否有这些方法
get<Key>, <key>, is<Key>, or _<key>
,如果有跳到5,没有走下一步 - 2.判断是否是
NSArray
- 2.1寻找是否实现
countOf<Key>
或者objectIn<Key>AtIndex:
方法,有的话就调用方法并返回值,没有就走3 - 3.是否是
NSSet
- 3.1 寻找是否实现
countOf<Key>, enumeratorOf<Key>, and memberOf<Key>:
方法,有的话就调用方法并返回值,没有就走4· - 4.非集合类型
- 4.1 判断
accessInstanceVariablesDirectly:
(是否可以直接访问成员变量:默认返回YES)方法是否返回YES
- 4.1 判断
- 4.2寻找
_<key>, _is<Key>, <key>, or is<Key>
这些成员变量
- 4.2寻找
- 4.3如果找到,直接获取实例变量的值,然后执行步骤5
- 5.细节处理
- 5.1如果检索到的属性值是对象指针,则只需要返回结果
- 5.2如果该值是
NSNumber
支持的标量类型,则将其存储在NSNumber
实例中并返回
- 5.2如果该值是
- 5.3如果该值是
NSNumber
不支持的标量类型,则转化为NSValue
对象并返回
- 5.3如果该值是
- 6.报错
valueForUndefinedKey:
setter方法- setValue:forKey:
- 1.简单式访问:寻找是否存在
set<Key>:
或者_set<Key>
的方法,如果有,那就调用,并完成;如果没有进入2 - 2.实例变量访问:
- 2.1 判断这个方法
accessInstanceVariablesDirectly
是否返回YES
- 2.1 判断这个方法
- 2.2 判断是否存在这些实例变量
_<key>, _is<Key>, <key>, or is<Key>
- 2.2 判断是否存在这些实例变量
- 2.3 按顺序找到就直接赋值,并完成,不再给下面的成员变量赋值
- 3.报错:如果1和2都有找到,那么就报错:
setValue:forUndefinedKey:
KVC其他功能
1.自动转换类型
在存值时如果value
的类型是NSString
类的数字、结构体类型,取值时value类型会进行相应的转化成__NSCFNumber 、NSConcreteValue
。eg:
Person类实现
typedef struct {
float x, y, z;
} ThreeFloats;
@interface Person : NSObject
@property (nonatomic, copy) NSString *subject;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) BOOL sex;
@property (nonatomic) ThreeFloats threeFloats;
@end
调用
Person *person = [[Person alloc] init];
// 1: KVC 自动转换类型
NSLog(@"******1: KVC - int -> NSNumber - 结构体******");
[person setValue:@18 forKey:@"age"];
// 上面那个表达 大家应该都会! 但是下面这样操作可以?
[person setValue:@"20" forKey:@"age"]; // int - string
NSLog(@"%@-%@",[person valueForKey:@"age"],[[person valueForKey:@"age"] class]);//__NSCFNumber
[person setValue:@"20" forKey:@"sex"];//Bool - string
NSLog(@"%@-%@",[person valueForKey:@"sex"],[[person valueForKey:@"sex"] class]);//__NSCFNumber
ThreeFloats floats = {1., 2., 3.};
NSValue *value = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)];
[person setValue:value forKey:@"threeFloats"];
NSLog(@"%@-%@",[person valueForKey:@"threeFloats"],[[person valueForKey:@"threeFloats"] class]);//NSConcreteValue
设置空值
NSLog(@"******2: 设置空值******");
[person setValue:nil forKey:@"age"]; //age 为nil
[person setValue:nil forKey:@"subject"];//subject不会走 - 官方注释里面说只对 NSNumber - NSValue,而NSString类型没有这个功能
找不到的 key
NSLog(@"******3: 找不到的 key******");
[person setValue:nil forKey:@"KC"];
取值时 - 找不到 key
NSLog(@"******4: 取值时 - 找不到 key******");
NSLog(@"%@",[person valueForKey:@"KC"]);
键值验证
KVC提供键值验证api
- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
NSLog(@"******5: 键值验证******");
NSError *error;
NSString *name = @"Cooci";
if (![person validateValue:&name forKey:@"names" error:&error]) {
NSLog(@"%@",error);
}else{
NSLog(@"%@",[person valueForKey:@"name"]);
}