KVO底层实现分析

此文章为引用。地址:https://www.jianshu.com/p/d6faa91a9721

1 KVO 的基本使用

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

self.person = [[Person alloc] init];...NSKeyValueObservingOptionsoptions =NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld;[self.person addObserver:selfforKeyPath:@"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 类对象

证明方式:

断点打印

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

通过 runtime 的 object_getClass() 方法获取实例对象对应的类对象,证明person 实例对象的类对象确实被系统改写了

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

对比证明系统会给添加了 KVO 的实例对象动态生成一个NSKVONotifying_Person类,且让该实例对象的 isa 指向这个新生成的类对象

参考《NSObject 对象占用内存、isa/superclass指向、类信息存放》,可知对象方法信息都是存放在类对象(class object)中的,而实例对象调用实例方法的流程就是通过实例对象的 isa 找到其指向的类对象,在类对象的实例方法列表内找到实例方法,然后调用。如下图所示

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

方法调用逻辑-引自MJ底层课件图片

系统改写了执行 KVO 的 person1 实例的 isa指针,让其指向了NSKVONotifying_Person类对象,这就说明,修改 person1的 age 属性时,调用的 setAge: 方法,实际上调用的是 NSKVONotifying_Person类对象里面的setAge:方法,而不是 Person 类对象的setAge:方法。

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

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

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

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

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

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

KVO 之前,调用的 setAge: 方法的地址一样

KVO 之后,调用的 setAge: 方法的地址不同

通过LLDB 命令,将方法地址转成名称形式显示

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

注意:

_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底层实现分析_第7张图片

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

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

关系如下:

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

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

通过在 NSKVONotifying_Person 的父类 Person 中(3.2中证明了),重写willChangeValueForKey: 和 didChangeValueForKey:方法,拦截调用顺序,具体代码如图:

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

点击屏幕,改变 person1的 age 属性值,对比 log 日志打印信息

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

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

NSKVONotifying_Person的父类为 Person 类,当在 Person 类类中重写了上述方法,那么子类可以通过 superclass 指针优先找到Person类中方法实现

willChangeValueForKey: 和 didChangeValueForKey:方法是属于NSObject 的NSObject(NSKeyValueObserverNotification)分类里的方法,此时的NSObject 是 Person 的父类,按照实例方法的查找调用循序来看,当然会优先调用 Person 中重写的方法,而 Person 中的方法仅仅是重写拦截,内部还会通过 super 调用父类的方法,所以对原有的 NSObject 的分类方法是没有影响的。

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

通过log 日志可知,_NSSet***ValueAndNotify 的内部实现为:

调用willChangeValueForKey:

调用原来的setter实现

调用didChangeValueForKey:

- didChangeValueForKey:内部会调用observer的observeValueForKeyPath:ofObject:change:context:方法

4 相关题:

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

利用Runtime API动态生成一个子类,并且让instance对象的isa指向这个全新的子类

当修改instance对象的属性时,会调用Foundation的_NSSetXXXValueAndNotify函数

_NSSet***ValueAndNotify 的内部实现为:

调用willChangeValueForKey:

调用原来的setter实现

调用didChangeValueForKey:

- didChangeValueForKey:内部会调用observer的observeValueForKeyPath:ofObject:change:context:方法

4.2 如何手动触发KVO?

手动调用willChangeValueForKey:和didChangeValueForKey:

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

image.png

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

不会触发KVO

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

暴露成员变量

直接修改成员变量,没有触发监听方法

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

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