KVO底层实现分析

KVO底层实现分析_第1张图片

注:分析参考 MJ底层原理班 内容,本着自己学习原则记录

1 KVO 的基本使用

1.1 给 Person 的实例对象 person 添加observer,监听 person 的 age 属性

self.person = [[Person alloc] init];
...
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person addObserver:self forKeyPath:@"age" options:options context:@"123"];

1.2 当 person 的 age 属性发生变化时,会调用 observer 的回调方法(也就是NSObject(NSKeyValueObserving)分类的方法)

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
}

2 NSKVONotifying_Person

2.1 执行 KVO 的person1对象,它的 isa 所指向的类对象不再是 Person 类对象,而是 NSKVONotifying_Person 类对象

证明方式:

  1. 断点打印


    KVO底层实现分析_第2张图片
  2. 通过 runtime 的 object_getClass() 方法获取实例对象对应的类对象,证明person 实例对象的类对象确实被系统改写了


    KVO底层实现分析_第3张图片
  • 对比证明系统会给添加了 KVO 的实例对象动态生成一个NSKVONotifying_Person类,且让该实例对象的 isa 指向这个新生成的类对象
  • 参考《NSObject 对象占用内存、isa/superclass指向、类信息存放》,可知对象方法信息都是存放在类对象(class object)中的,而实例对象调用实例方法的流程就是通过实例对象的 isa 找到其指向的类对象,在类对象的实例方法列表内找到实例方法,然后调用。如下图所示
    KVO底层实现分析_第4张图片
    方法调用逻辑-引自MJ底层课件图片
  • 系统改写了执行 KVO 的 person1 实例的 isa指针,让其指向了NSKVONotifying_Person类对象,这就说明,修改 person1的 age 属性时,调用的 setAge: 方法,实际上调用的是 NSKVONotifying_Person类对象里面的setAge:方法,而不是 Person 类对象的setAge:方法。

3 NSKVONotifying_Person 中 setAge: 的内部执行逻辑猜想

  • NSKVONotifying_Person类中的 setAge: 实现方法猜想逻辑


    KVO底层实现分析_第5张图片

3.1 证明setAge:方法内部调用的是 _NSSetIntValueAndNotify() 方法

  1. 通过打印 setAge: 方法的地址判断前后变化


    KVO底层实现分析_第6张图片
  • KVO 之前,调用的 setAge: 方法的地址一样
  • KVO 之后,调用的 setAge: 方法的地址不同
  1. 通过LLDB 命令,将方法地址转成名称形式显示


    KVO底层实现分析_第7张图片

注意:
_NSSetIntValueAndNotify()是会根据监听属性的类型进行变化的,前面 age 属性的描述为@property (assign, nonatomic) int age;。当 age 属性描述变更为 double 的话,即@property (assign, nonatomic) double age;,setAge:方法内调用的方法为_NSSetDoubleValueAndNotify()

3.2 证明 NSKVONotifying_Person 的isa 指向什么?(是 Person 的元类对像吗?)

  • 第2.1的第2点中说明,person1的 isa 指向 NSKVONotifying_Person,而 person2 的 isa 指向 Person
  • 那么NSKVONotifying_Person的 isa 指向?Person 的 isa指向?答案如下图:
    NSKVONotifying_Person的元类对象是自己本身,而 Person 的元类对象也是 Person

注意object_getClass(object_getClass(self.person1)) 表示,获取 self.person1的元类对象,即第一次object_getClass(self.person1)是获取 person1的类对象,第二次object_getClass则是获取类对象的元类对象。

KVO底层实现分析_第8张图片

3.3 证明 NSKVONotifying_Person 的 superClass 指向什么?(是 Person 类吗?)

KVO底层实现分析_第9张图片

关系如下:


KVO底层实现分析_第10张图片

3.4 证明 _NSSetIntValueAndNotify()方法内部实现确实如猜想代码中的调用逻辑

  1. 通过在 NSKVONotifying_Person 的父类 Person 中(3.2中证明了),重写willChangeValueForKey:didChangeValueForKey:方法,拦截调用顺序,具体代码如图:
    KVO底层实现分析_第11张图片
  2. 点击屏幕,改变 person1的 age 属性值,对比 log 日志打印信息


    KVO底层实现分析_第12张图片

为什么要通过在 Person 中重写上述方法拦截打印来证明呢?

  1. NSKVONotifying_Person的父类为 Person 类,当在 Person 类类中重写了上述方法,那么子类可以通过 superclass 指针优先找到Person类中方法实现
  2. willChangeValueForKey:didChangeValueForKey:方法是属于NSObject 的NSObject(NSKeyValueObserverNotification)分类里的方法,此时的NSObject 是 Person 的父类,按照实例方法的查找调用循序来看,当然会优先调用 Person 中重写的方法,而 Person 中的方法仅仅是重写拦截,内部还会通过 super 调用父类的方法,所以对原有的 NSObject 的分类方法是没有影响的。
    KVO底层实现分析_第13张图片
  1. 通过log 日志可知,_NSSet***ValueAndNotify 的内部实现为:
  • 调用willChangeValueForKey:
  • 调用原来的setter实现
  • 调用didChangeValueForKey:
    - didChangeValueForKey:内部会调用observerobserveValueForKeyPath:ofObject:change:context:方法

4 相关题:

4.1 iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么?)

  1. 利用Runtime API动态生成一个子类,并且让instance对象的isa指向这个全新的子类
  2. 当修改instance对象的属性时,会调用Foundation的_NSSetXXXValueAndNotify函数
  3. _NSSet***ValueAndNotify 的内部实现为:
  • 调用willChangeValueForKey:
  • 调用原来的setter实现
  • 调用didChangeValueForKey:
    - didChangeValueForKey:内部会调用observerobserveValueForKeyPath:ofObject:change:context:方法

4.2 如何手动触发KVO?

  • 手动调用willChangeValueForKey:和didChangeValueForKey:


    KVO底层实现分析_第14张图片
    image.png

4.3 直接修改成员变量会触发KVO么?

  • 不会触发KVO


    KVO底层实现分析_第15张图片
    暴露成员变量
  • 直接修改成员变量,没有触发监听方法


    KVO底层实现分析_第16张图片

文/Jacob_LJ(作者)
PS:如非特别说明,所有文章均为原创作品,著作权归作者所有,转载需联系作者获得授权,并注明出处,所有打赏均归本人所有!

你可能感兴趣的:(KVO底层实现分析)