一、KVC
- KVC的全称是Key-Value-Coding, 俗称"键值编码", 可以通过一个key来访问某个属性
- 常见的API有:
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
- (void)setValue:(id)value forKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;
- (id)valueForKey:(NSString *)key;
- 创建命令行工程, 测试上面几个方法

- 创建
Person类继承自NSObject, 创建一个Cat类继承自NSObject Person有两个属性age和cat,Cat类有一个属性weight, 如下图:

- 使用
- (void)setValue:(id)value forKey:(NSString *)key;方法, 可以设置实例中的属性值 - 使用
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;方法, 可以设置实例中, 自定义属性对象中的属性值 - 使用
- (id)valueForKey:(NSString *)key;方法, 可以获取实例中的属性值 - 使用
- (id)valueForKeyPath:(NSString *)keyPath;方法, 可以获取实例中的自定义属性对象中的属性值

二、KVC赋值时, 方法和属性的顺序
-
使用
KVC给一个对象赋值时, 会有以下方法和属性的调用顺序- 查看
setKey:方法是否存在, 如果存在直接调用, 如果不存在进入下一步 - 查看
_setKey:方法是否存在, 如果存在直接调用, 如果不存在进入下一步 - 查看
+ (BOOL)accessInstanceVariablesDirectly方法的返回值, 默认返回YES- YES: 可以访问成员变量, 进入下一步
- NO: 不可以访问成员变量, 同时调用
- (void)setValue:(id)value forUndefinedKey:(NSString *)key方法, 如果方法不存在会抛出异常
- 调用成员变量:
_key,_isKey,key,isKey- 调用顺序, 从左到右, 只有发现存在成员变量, 就不会在调用后续变量
- 如果没有成员变量, 会调用
- (void)setValue:(id)value forUndefinedKey:(NSString *)key方法, 如果方法不存在会抛出异常
- 查看
-
下面验证上面的调用顺序:
1、准备代码
- 现在将Person类修改成下图的样子, 同时移除
Cat类和cat以及age属性, 并添加下面的四个方法

- 在
main.m文件中, 继续给person调用KVC赋值age属性值为10

2、验证调用setKey:方法
- 运行程序, 效果如下, 很明显, 调用了
setAge:方法

3、在setKey:方法不存在时, 调用_setKey:方法
- 注销
setAge:方法, 运行程序, 效果如下, 调用了_setAge:方法

4: 当setKey:和_setKey:都不存在时, 会调用+ (BOOL)accessInstanceVariablesDirectly方法
- 注销
_setAge:, 运行程序, 效果如下, 调用了accessInstanceVariablesDirectly方法, 由于方法返回NO, 会调用- (void)setValue:(id)value forUndefinedKey:(NSString *)key方法

- 当
accessInstanceVariablesDirectly方法返回YES时, 会依次查看_key,_isKey,key,isKey四个成员变量, 如果成员变量不存在, 会调用- (void)setValue:(id)value forUndefinedKey:(NSString *)key方法

5、成员变量的调用顺序
- 上面说过:
setKey:和_setKey都不存在时, 会调用accessInstanceVariablesDirectly方法, 如果accessInstanceVariablesDirectly返回YES, 会访问成员变量 - 现在添加四个成员变量, 并删除多余代码, 如下图

- 下面验证成员变量会按照
_key,_isKey,key,isKey的顺序调用:




- 上面四张图片, 验证了属性的访问顺序
- 当然有人可能会说, 这是因为声明属性时, 按照了固定顺序
- 接下来打乱顺序, 重新验证




- 很明显, 属性的访问顺序, 与属性声明时的顺序无关
6、既没有方法, 也没有成员变量时, 程序触发运行时错误

7、如果setAge:和成员变量同时存在, 不会再访问成员变量

8、总结setValue:forKey:的原理

注意: accessInstanceVariablesDirectly方法的默认返回值是YES
三、KVC取值时, 方法和成员变量的调用顺序
- KVC取值时, 方法和成员变量的调用顺序如下:
- 判断是否有这几个方法:
getKey,key,isKey,_key- 从左到右, 如果有方法, 直接调用, 取值结束
- 如果没有进入下一步
- 调用
+ (BOOL)accessInstanceVariablesDirectly查看是否可以访问成员变量. 默认YESYES: 可以访问成员变量, 进入下一步NO: 不可以访问成员变量, 判断是否实现- (id)valueForUndefinedKey:(NSString *)key方法, 实现时调用, 未实现报错
- 判断是否有这几个成员变量:
_key,_isKey,key,isKey- 从左到右, 如果有成员变量, 直接访问, 取值结束
- 如果没有这几个成员变量, 直接进入下一步
- 判断是否实现
- (id)valueForUndefinedKey:(NSString *)key方法, 实现时调用, 未实现报错
- 判断是否有这几个方法:
1、准备代码
- 设置
Person中的代码, 如下图

main.m文件中代码如下图:

2、当存在getKey方法时, 直接调用方法

3、当不存在getKey方法时, 就会去判断key方法, 存在就会调用

4、getKey和key方法都不存在时, 判断isKey方法, 存在就会调用

5、当isKey方法也不存在, 就会判断_key方法, 存在就会调用

6、如果_key方法不存在时, 就回去判断+ (BOOL)accessInstanceVariablesDirectly方法的返回值, 默认YES
- 如果返回
NO, 会判断是否存在- (id)valueForUndefinedKey:(NSString *)key方法, 方法存在, 直接调用

- 如果方法
- (id)valueForUndefinedKey:(NSString *)key不存在, 直接触发运行时错误

- 当
+ (BOOL)accessInstanceVariablesDirectly方法返回YES时, 去判断成员变量
7、移除多余代码, Person代码如下, 用来验证成员变量取值顺序

8、验证KVC取值访问成员变量的顺序为_key, _isKey, key, isKey




注意: KVC取值时, 访问成员变量的顺序固定为
_key,_isKey,key,isKey
9、如果不存在这四个中的任意一个成员变量, 就会调用- (id)valueForUndefinedKey:(NSString *)key方法

10、如果方法也不存在, 直接触发运行时错误

11、总结valueForKey:的原理

四、KVC相关面试题
1、KVC赋值时, 会触发KVO吗?
- 将
Person代码改为如下图所示:

- 创建
Observer类自己成NSObject, 用来监听Person中的属性值改变

main.m中, 使用KVC给person的weight属性赋值

-
根据打印可以知道, 使用KVC给属性赋值, 可以触发
KVO -
接下来给
person的成员变量_age赋值

- 很明显, 直接给
person的成员变量_age赋值, 也会触发KVO - 我们知道, 触发
KVO时会调用willChangeValueForKey:和didChangeValueForKey两个方法 - 现在, 在
Person.m加入下面的代码

- 再次运行程序, 可以发现通过KVC赋值时, 系统会自动调用
KVO的这两个方法

总结: 使用
KVO给属性或成员变量赋值时, 都会触发KVO, 系统会自动调用willChangeValueForKey:和didChangeValueForKey:两个方法
2、KVC赋值和取值的过程是什么? 原理是什么?
- 赋值过程

- 取值过程
