一、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
查看是否可以访问成员变量. 默认YES
YES
: 可以访问成员变量, 进入下一步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赋值和取值的过程是什么? 原理是什么?
- 赋值过程
- 取值过程