一、KVC是什么?
Key-Value-Coding, 即键值编码. 它是一种不通过存取方法, 而是通过属性名称字符串间接访问属性的机制.是指在 iOS 开发中,允许开发者通过 字符串键和键路径直接访问或赋值对象属性.
在Cocoa中是以被万物之源NSObject类实现的 NSKeyValueCoding 非正式协议的形式被定义为基础框架的一部分. 从协议的角度来说, KVC本质上是定义了一套让我们去遵守和实现的方法. 所以对于所有继承了NSObject的类型, 也就是几乎所有的Objective-C对象都能使用KVC.KVO 就是基于 KVC 实现的关键技术之一.
二、KVC主要方法
- KVC定义了一种按名称访问对象属性的机制,支持这种访问的主要方法是:
//直接通过Key来取值
- (nullable id)valueForKey:(NSString *)key;
//通过Key来设值
- (void)setValue:(nullable id)value forKey:(NSString *)key;
//通过KeyPath来取值
- (nullable id)valueForKeyPath:(NSString *)keyPath;
//通过KeyPath来设值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
前边两个方法用到的Key较容易理解, 就是要访问的属性名称对应的字符串.
后面两个方法用到的KeyPath是一个被点操作符隔开的用于访问对象的指定属性的字符串序列.
比如:
[person setValue:@100 forKeyPath:@"account.balance"];
将会访问消息接收对象(person)所包含的 account 属性中的 balance 属性, 并赋值 100.
其实KeyPath说白了就是我们平时使用点操作访问某个对象的属性时所写的那个字符串.
- 当然
NSKeyValueCoding
类别中还有其他的一些方法:
//默认返回YES, 表示如果没有找到Set<Key>方法的话, 会按照_key, _iskey, key, iskey的顺序搜索成员, 设置成NO就不这样搜索
+ (BOOL)accessInstanceVariablesDirectly;
//KVC提供属性值正确性验证的API, 它可以用来检查set的值是否正确, 为不正确的值做一个替换值或者拒绝设置新值并返回错误原因.
- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
//这是集合操作的API, 里面还有一系列这样的API, 如果属性是一个NSMutableArray, 那么可以用这个方法来返回
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
//如果Key不存在, 且没有KVC无法搜索到任何和Key有关的字段或者属性, 则会调用这个方法, 默认是抛出异常
- (nullable id)valueForUndefinedKey:(NSString *)key;
//和上一个方法一样, 但这个方法是设值
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
//如果你在SetValue方法时面给Value传nil, 则会调用这个方法
- (void)setNilValueForKey:(NSString *)key;
//输入一组key,返回该组key对应的Value, 再转成字典返回,用于将Model转到字典
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
三、KVC是如何通过 key 来访问对象属性的?
KVC 在某种程度上提供了替代存取方法(访问器方法)的方案.不过存取方法终究是个好东西, 以至于只要有可能, KVC 也尽可能先尝试使用存取方法访问属性. 当 KVC 访问属性时, 其内部做了很多事.
- setValue:forKey:搜索方式
1、首先搜索setKey:
方法.(key
指成员变量名,首字母大写)
2、上面的setter
方法没找到, 如果类方法accessInstanceVariablesDirectly
返回 YES(NSKeyValueCodingCatogery
中实现的类方法, 默认实现为返回YES). 那么按_key, _isKey, key, iskey
的顺序搜索成员名.
3、如果没有找到成员变量,调用setValue: forUndefinedKey:
默认抛出异常.- valueForKey:的搜索方式
1、首先按getKey, key, isKey
的顺序查找getter
方法, 找到直接调用. 如果是BOOL、int
等数据类型,会做NSNumber
的转换.
2、上面的getter
没找到, 查找countOfKey、objectInKeyAtindex、KeyAtindexes
格式的方法. 如果countOfKey
和另外两个方法中的一个找到, 那么就会返回一个可以响应NSArray
所有方法的代理集合的NSArray
消息方法.
3、还没找到, 查找countOfKey、enumeratorOfKey、memberOfKey
格式的方法. 如果这三个方法都找到, 那么就返回一个可以响应NSSet
所有方法的代理集合.
4、还是没找到, 如果类方法accessInstanceVariablesDirectly
返回 YES. 那么按_key, _isKey, key, iskey
的顺序搜索成员名.
5、再没找到, 调用valueForUndefinedKey:
默认抛出异常.
当然, 如果开发者想让这个类禁用 KVC 里,那么重写 + (BOOL)accessInstanceVariablesDirectly
方法让其返回 NO
即可,这样的话如果 KVC 没有找到 set<Key>:
属性名时,会直接用 setValue:forUndefinedKey:
方法。
四、KVC异常处理
需要注意的是:
- value 赋值为 nil
如果给 value 赋值一个 nil,KVC 会调用setNilValueForKey:
方法。这个方法默认是抛出异常,所以一般而言最好还是重写这个方法。 - key 书写错误
一旦使用 KVC 你的编译器无法检查出错误, 即不会对设置的键、键路径进行错误检查. 所以, 如果不小心书写了错误的 key, KVC 会调用setValueForKey:
方法. 这个方法默认是抛出异常, 将会直接导致程序崩溃, 所以一般而言最好还是重写这个方法.