先抛出问题
- iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么?)
答. 当一个对象使用了KVO监听,iOS系统会修改这个对象的isa指针,改为指向一个全新的通过Runtime动态创建的子类,子类拥有自己的set方法实现,set方法实现内部会顺序调用willChangeValueForKey方法、原来的setter方法实现、didChangeValueForKey方法,而didChangeValueForKey方法内部又会调用监听器的observeValueForKeyPath:ofObject:change:context:监听方法。
- 如何手动触发KVO
答. 被监听的属性的值被修改时,就会自动触发KVO。如果想要手动触发KVO,则需要我们自己调用willChangeValueForKey和didChangeValueForKey方法即可在不改变属性值的情况下手动触发KVO,并且这两个方法缺一不可。
KVO底层实现分析(感谢lmj)
- 先写个例子
Person *p1 = [[Person alloc] init];
Person *p2 = [[Person alloc] init];
p1.age = 1;
p1.age = 2;
p2.age = 2;
// self 监听 p1的 age属性
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[p1 addObserver:**self** forKeyPath:@"age" options:options context:**nil**];
p1.age = 10;
[p1 removeObserver:**self** forKeyPath:@"age"];
}
- (**void**)observeValueForKeyPath:(NSString *)keyPath ofObject:(**id**)object change:(NSDictionary<NSKeyValueChangeKey,**id**> *)change context:(**void** *)context {
NSLog(@"监听到%@的%@改变了%@", object, keyPath,change);
}
打印如下图
对比了p1和p2俩,加了观察者,p1的isa指针有变化,指向的是一个新的对象NSKVONotifyin_Person,OC对象的方法调用是依赖isa指针的,所以setAge这个方法执行有差别了.啥差别.顺便先回顾一下OC的消息发送流程.抄个图,嘻嘻
p2调用setAge是通过isa去类对象的方法列表找setAge;但是p1的指针已经指向了一个新的NSKVONotifyin_Person(NSPerson的子类,runtime在运行时生成),NSKVONotifyin_Person这里面重写了属性的set方法,他的具体实现主要是在父类的set方法前后添加了_NSsetIntValueAndNotify和didChangeValueForKey,didChangeValueForKey这里面就会调用监听. NSKVONotifyin_Person中的setage方法中其实调用了 Fundation框架中C语言函数 _NSsetIntValueAndNotify,_NSsetIntValueAndNotify内部做的操作相当于,首先调用willChangeValueForKey将要改变方法,之后调用父类的setage方法对成员变量赋值,最后调用didChangeValueForKey已经改变方法。didChangeValueForKey中会调用监听器的监听方法,最终来到监听者的observeValueForKeyPath方法中
再来一张图,不得不说这张图画的真的好,再次致谢lmj
拓展一下
在方法调用的过程中可以分为三个阶段。
消息发送阶段:负责从类及父类的缓存列表及方法列表查找方法。
动态解析阶段:如果消息发送阶段没有找到方法,则会进入动态解析阶段,负责动态的添加方法实现。
消息转发阶段:如果也没有实现动态解析方法,则会进行消息转发阶段,将消息转发给可以处理消息的接受者来处理.如果消息转发也没有实现,就会报方法找不到的错误,无法识别消息,unrecognzied selector sent to instance
ps: 查阅类和方法的资料发现一个我一直搞错的点,本来以为成员变量是放在实例对象,其实不是,成员变量的值放在实例对象,其他信息在类对象中.why? 成员变量的值是存储在实例对象中的,因为只有当我们创建实例对象的时候才为成员变赋值。但是成员变量叫什么名字,是什么类型,只需要有一份就可以了。所以存储在class对象中。