小码哥iOS学习笔记第五天: KVC的本质(调用顺序)

一、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有两个属性agecat, 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、getKeykey方法都不存在时, 判断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中, 使用KVCpersonweight属性赋值

  • 根据打印可以知道, 使用KVC给属性赋值, 可以触发KVO

  • 接下来给person的成员变量_age赋值

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

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

总结: 使用KVO给属性或成员变量赋值时, 都会触发KVO, 系统会自动调用willChangeValueForKey:didChangeValueForKey:两个方法

2、KVC赋值和取值的过程是什么? 原理是什么?

  • 赋值过程

  • 取值过程

你可能感兴趣的:(小码哥iOS学习笔记第五天: KVC的本质(调用顺序))