此文章为引用。地址: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 类对象
证明方式:
断点打印
通过 runtime 的 object_getClass() 方法获取实例对象对应的类对象,证明person 实例对象的类对象确实被系统改写了
对比证明系统会给添加了 KVO 的实例对象动态生成一个NSKVONotifying_Person类,且让该实例对象的 isa 指向这个新生成的类对象
参考《NSObject 对象占用内存、isa/superclass指向、类信息存放》,可知对象方法信息都是存放在类对象(class object)中的,而实例对象调用实例方法的流程就是通过实例对象的 isa 找到其指向的类对象,在类对象的实例方法列表内找到实例方法,然后调用。如下图所示
方法调用逻辑-引自MJ底层课件图片
系统改写了执行 KVO 的 person1 实例的 isa指针,让其指向了NSKVONotifying_Person类对象,这就说明,修改 person1的 age 属性时,调用的 setAge: 方法,实际上调用的是 NSKVONotifying_Person类对象里面的setAge:方法,而不是 Person 类对象的setAge:方法。
3 NSKVONotifying_Person 中 setAge: 的内部执行逻辑猜想
NSKVONotifying_Person类中的 setAge: 实现方法猜想逻辑
3.1 证明setAge:方法内部调用的是 _NSSetIntValueAndNotify() 方法
通过打印 setAge: 方法的地址判断前后变化
KVO 之前,调用的 setAge: 方法的地址一样
KVO 之后,调用的 setAge: 方法的地址不同
通过LLDB 命令,将方法地址转成名称形式显示
注意:
_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则是获取类对象的元类对象。
3.3 证明 NSKVONotifying_Person 的 superClass 指向什么?(是 Person 类吗?)
关系如下:
3.4 证明 _NSSetIntValueAndNotify()方法内部实现确实如猜想代码中的调用逻辑
通过在 NSKVONotifying_Person 的父类 Person 中(3.2中证明了),重写willChangeValueForKey: 和 didChangeValueForKey:方法,拦截调用顺序,具体代码如图:
点击屏幕,改变 person1的 age 属性值,对比 log 日志打印信息
为什么要通过在 Person 中重写上述方法拦截打印来证明呢?
NSKVONotifying_Person的父类为 Person 类,当在 Person 类类中重写了上述方法,那么子类可以通过 superclass 指针优先找到Person类中方法实现
willChangeValueForKey: 和 didChangeValueForKey:方法是属于NSObject 的NSObject(NSKeyValueObserverNotification)分类里的方法,此时的NSObject 是 Person 的父类,按照实例方法的查找调用循序来看,当然会优先调用 Person 中重写的方法,而 Person 中的方法仅仅是重写拦截,内部还会通过 super 调用父类的方法,所以对原有的 NSObject 的分类方法是没有影响的。
通过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:
image.png
4.3 直接修改成员变量会触发KVO么?
不会触发KVO
暴露成员变量
直接修改成员变量,没有触发监听方法