iOS 底层原理记录 02 – KVO

发布于 2022-08-12  84 次阅读


封面首图



KVO

KVO 的全程是 Key-Value Observing ,也就是 键值监听
一个对象增加了 KVO 监听,则它实例对象的 ISA 指向新的 NSKVONotifying_类对象(Runtime 动态创建的一个类,是原来类对象的子类)。
如果没增加监听,则isa指向原来的类对象。
新的子类的set属性方法,会调用 Foundation 框架中的 _NSSetIntValueAndNotify() (根据数据类型) 这个 C 语言方法,大致如下:

    [self willChangeValueForKey:@"xxx"];
    [super setAge:xxx];
    [self didChangeValueForKey:@"xxx"];

调试相关

  1. 通过methodForSelector方法,可以得到某个方法的 IMP ,应该是 IMP 地址
  2. 通过断点,p (IMP)xxxx这种,可以得到具体实现

面试题

  1. KVO 的本质是什么?
    答:
    利用 RuntimeAPI 动态生成一个子类,并且让 instance 对象的 isa 指向这个全新的子类。
    当修改 instance 对象的属性时,会调用 Foundation 框架的 _NSSetXXXValueAndNotify 函数:

    内部会先调用 willChangeValueForKey:
    然后调用父类原来的 setter 方法
    然后再调用 didChangeValueForKey:
    这个方法(didChangeValueForKey)内部会触发监听器(Observer)的监听方法(observerValueForKeyPath:ofObject:change:context:)
    
  2. 如何手动触发 KVO
    答:
    手动调用 willChangeValueforKey 以及 didChangeValueForKey 这两个方法(缺一不可,因为后面的那个方法会判断前面的方法有没有调用)

  3. 直接修改成员变量会触发 KVO 吗?
    答:
    不会。因为 KVO 的本质就是新的子类重写了属性的 setter 方法,而直接修改变量值,不会走这个 setter 方法。


KVC

KVC 的全称是 Key-Value Coding, 俗称键值编码,可以通过一个 key 来访问某个属性。

常见的 KVC 的 API

- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;

通过 keyPath 可以设置多层对象中的属性值,比如下面这句: keyPath 是"a.b.c.age",那么修改的就是 a 对象中的 b 对象 中的 c 对象的 age 属性。
[a setValue:(id)@(10) forKeyPath:"b.c.age"]

- (void)setValue:(id)value forKey:(NSString *)key;

通过 key 可以设置对象中属性值,比如 key 传 "name",那么就可以修改对象的 name 属性值。如下:
[a setValue:(id)@"小明" forKey:@"name"]

- (id)valueForKeyPath:(NSString *)keyPath;

取值,使用方法同上

- (id)valueForKey:(NSString *)key;

取值,使用方法同上


KVC 的赋值和取值原理

setValueForKey 的原理 既设置值原理

  1. 首先,直接调用 setKey:_setKey: 方法,找到则调用。
  2. 如果没有找到方法,则查看 accessInstanceVariablesDirectly的返回值(是否允许直接访问成员变量,默认是YES)如果返回 NO ,则调用 setValue:forUndefineKey: 并抛出 NSUnknownKeyException的异常。
  3. 如果返回的是 YES ,则按照_key_isKeykeyisKey的顺序查找成员变量,找到就直接赋值。如果没有,则调用 setValue:forUndefineKey: 并抛出 NSUnknownKeyException的异常。

getValueForKey 的原理 既取值原理

  1. 首先,按照 getKeykeyisKey_key 顺序查找方法,找到就直接调用取值。
  2. 如果没有找到方法,则查看 accessInstanceVariablesDirectly的返回值(是否允许直接访问成员变量,默认是YES)如果返回 NO ,则调用 setValue:forUndefineKey: 并抛出 NSUnknownKeyException的异常。
  3. 如果返回的是 YES ,则按照_key_isKeykeyisKey的顺序查找成员变量,找到就直接取值。如果没有,则调用 setValue:forUndefineKey: 并抛出 NSUnknownKeyException的异常。

面试题

  1. 通过 KVC 修改属性会触发 KVO 吗?
    答:
    会触发 KVO 。相当于 KVC 内部自动调用了 willChangeValueForKey 以及 didChangeValueForKey 这2个方法,触发

  2. KVC 的赋值和取值过程是怎样的?原理是什么?
    答:
    参照上方赋值、取值原理。


迷雾尽散 破晓而生