定义 KVC(Key-Value Coding)是利用NSKeyValueCoding 非正式协议实现的一种机制,
对象采用这种机制来提供对其属性的间接访问.
1、KVC的全称是Key-Value Coding、俗称“键值编码”、可以通过一个key来访问某一个属性。
2、常见的API有四种
setValue: forKey
setValue: forKeyPath
valueForKey:
valueForKeyPath
前两种是赋值 后两种是取值 forKey 和 forKeyPath 主要区别是路径区别如下
例:有2个类(Person和Dog)
Person类有2个属性 name 和 dog
Dog类 中也有一个name属性 如下:
@interface FCPerson : NSObject
@property(nonatomic,strong)NSString *name;
@property(nonatomic,strong)Dog *dog;
@end
@interface Dog : NSObject
@property(nonatomic,strong)NSString *name;
@end
我们使用 setValue: forKey 对 Person name赋值
使用 和 setValue: forKeyPath对 Dog name赋值 如下所示:
FCPerson * person = [[FCPerson alloc]init];
person.dog = [[Dog alloc]init];
[person setValue:@"张三" forKey:@"name"];
[person setValue:@"小狗狗" forKeyPath:@"dog.name"];
NSLog(@"人名:%@---狗名:%@",person.name,person.dog.name);
最后打印的结果就是 人名:张三---狗名:小狗狗
如果直接使用 setValue: forKey 的方法对狗狗name赋值就会报错
!! forKeyPath支持路径查找,forKey不支持
使用范围
-
KVC是通过对NSObject的扩展来实现的 —— 只要继承了NSObject的类都可以使用KVC -
NSArray、NSDictionary、NSMutableDictionary、NSOrderedSet、NSSet等也遵守KVC协议 -
除少数类型(结构体)以外都可以使用
KVCTCJPerson *person = [TCJPerson alloc]; [person setValue:@"TCJ" forKey:@"name"]; [person setValue:@"CJ" forKey:@"nickName"];
KVC常用方法,这些也是我们在日常开发中经常用到的
// 通过 key 设值
- (void)setValue:(nullable id)value forKey:(NSString *)key;
// 通过 key 取值
- (nullable id)valueForKey:(NSString *)key;
// 通过 keyPath 设值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
// 通过 keyPath 取值
- (nullable id)valueForKeyPath:(NSString *)keyPath;
NSKeyValueCoding类别的其它方法
// 默认为YES。 如果返回为YES,如果没有找到 set<Key> 方法的话,
// 会按照_key, _isKey, key, isKey的顺序搜索成员变量, 返回NO则不会搜索
+ (BOOL)accessInstanceVariablesDirectly;
// 键值验证, 可以通过该方法检验键值的正确性, 然后做出相应的处理
- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue
forKey:(NSString *)inKey
error:(out NSError **)outError;
// 如果key不存在, 并且没有搜索到和key有关的字段, 会调用此方法, 默认抛出异常。
// 两个方法分别对应 get 和 set 的情况
- (nullable id)valueForUndefinedKey:(NSString *)key;
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
// setValue方法传 nil 时调用的方法
// 注意文档说明: 当且仅当 NSNumber 和 NSValue 类型时才会调用此方法
- (void)setNilValueForKey:(NSString *)key;
// 一组 key对应的value, 将其转成字典返回, 可用于将 Model 转成字典
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
三、KVC底层原理
由于NSKeyValueCoding的实现在Foundation框架中,但它又不开源,我们只能通过KVC官方文档来了解它
- ①.按
set<Key>:、_set<Key>:顺序查找对象中是否有对应的方法- 找到了直接调用设值
- 没有找到跳转第2步
- ②.判断
accessInstanceVariablesDirectly结果- 为YES时按照
_<key>、_is<Key>、<key>、is<Key>的顺序查找成员变量,找到了就赋值;找不到就跳转第3步 - 为NO时跳转第3步
- 为YES时按照
- ③.调用
setValue:forUndefinedKey:。默认情况下会引发一个异常,但是继承于NSObject的子类可以重写该方法就可以避免崩溃并做出相应措施
取值过程:
-
①.按照
get<Key>、<key>、is<Key>、_<key>顺序查找对象中是否有对应的方法- 如果有则调用getter,执行第5步
- 如果没有找到,跳转到第2步
-
②.查找是否有
countOf<Key>和objectIn<Key>AtIndex:方法(对应于NSArray类定义的原始方法)以及<key>AtIndexes:方法(对应于NSArray方法objectsAtIndexes:)- 如果找到其中的第一个(
countOf<Key>),再找到其他两个中的至少一个,则创建一个响应所有NSArray方法的代理集合对象,并返回该对象(即要么是countOf<Key> + objectIn<Key>AtIndex:,要么是countOf<Key> + <key>AtIndexes:,要么是countOf<Key> + objectIn<Key>AtIndex: + <key>AtIndexes:) - 如果没有找到,跳转到第3步
- 如果找到其中的第一个(
-
③.查找名为
countOf<Key>、enumeratorOf<Key>和memberOf<Key>这三个方法(对应于NSSet类定义的原始方法)- 如果找到这三个方法,则创建一个响应所有NSSet方法的代理集合对象,并返回该对象
- 如果没有找到,跳转到第4步
-
④.判断
accessInstanceVariablesDirectly- 为YES时按照
_<key>、_is<Key>、<key>、is<Key>的顺序查找成员变量,找到了就取值 - 为NO时跳转第6步
- 为YES时按照
-
⑤.判断取出的属性值
- 属性值是对象,直接返回
- 属性值不是对象,但是可以转化为
NSNumber类型,则将属性值转化为NSNumber类型返回 - 属性值不是对象,也不能转化为
NSNumber类型,则将属性值转化为NSValue类型返回
-
⑥.调用
valueForUndefinedKey:.默认情况下会引发一个异常,但是继承于NSObject的子类可以重写该方法就可以避免崩溃并做出相应措施
四、自定义KVC
根据
KVC的设值过程、取值过程,我们可以自定义KVC的setter方法和getter方法,但是这一切都是根据官方文档做出的规则,自定义KVC只能在一定程度上取代系统KVC,大致流程几乎一致:实现了setValue:forUndefinedKey:、valueForUndefinedKey:的调用,且accessInstanceVariablesDirectly无论为true为false,都能保持两次调用
①.自定义setter方法
1. key 非空判断
2. 2.找到相关方法set<Key>、_set<Key>、setIs<Key>,若存在就直接调用
3.判断是否能够直接赋值实例变量,不能的情况下就调用setValue:forUndefinedKey:或抛出异常
4.找相关实例变量进行赋值
5.调用setValue:forUndefinedKey:或抛出异常