一、KVO的实现原理
KVO的全称是Key-ValueObserving(键值监听),可以用于监听某个对象属性值的改变。
1、KVO的使用API如下
通过addObserver: forKeyPath: options: context:添加观察者对某个属性的监听。
2、大家看下我写的代码,在设置age打个断点来调试。
为什么会这样?明明我的person实例是HPPerson类实例化而来的切发现他的类对象不再是HPPerson了,变成了NSKVONotifying_HPPerson。person1的类对象还是HPPerson。
注意:不能用[self.person class]来获取解析本质。[self.person class],[self.person1 class]得到的结果都是HPPerson类,这个可能是苹果API不想你知道太多内层的实现哈哈。所以要利用isa指针的本质来看问题。
说明:在使用[self.person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];时,在内部实现中通过Runtime动态生成了一个NSKVONotifying_HPPerson类。
3、NSKVONotifying_HPPerson类是什么类?
先看下setAge的实现。
可以看出,由于person加了KVO监听,所以setAge的函数地址变了,实现也变了!setAge里面的具体实现是调用了_NSSetIntValueAndNotify()这个函数。但是我们从self.person.age=10;的设置结果可以看出,self.person.age设值后的age确实是10;也就是说这个成员变量_age是正确按照setAge:里面的实现(_age=age)做了赋值操作。而这个赋值操作时在Person类对象的方法列表中的setAge方法里面。说明_NSSetIntValueAndNotify()函数应该实现了setAge的方法实现。也就是要达到这种目的,只有一种情况,NSKVONotifying_HPPerson是HPPerson的子类。也就是在addObserver:时候Runtime动态创建的NSKVONotifying_HPPerson类是HPPerson类的子类。
可以通过LLDB打印他的superclass指针来证明NSKVONotifying_HPPerson确实是HPPerson子类。
由此可以看出添加KVO后的实例对象的类对象变成了NSKVONotifying_HPPerson类,而没有添加KVO的实例对象的类对象还是之前的HPPerson类。
总结关系如下图:
1)添加KVO的类关系图谱
2)没有添加KVO的类关系图谱
3)如果增加一个stirng类型的name属性,则会调用_NSSetCharValueAndNotify(),由此可以知道_NSSet**ValueAndNotify()有非常多个这个函数,根据不同类型属性调用不同函数名的函数,这个属于Foundation框架下的函数,可以通过越狱设备拿到Foundation的二进制文件,然后反编译后去查找这类函数的实现。过程太复杂,这里不深入写下去了。 在这里只要知道我们的KVO实质是利用了Runtime生成了一个子类,在子类的setAge方法中调用_NSSetIntValueAndNotify()来实现的。
4)_NSSetIntValueAndNotify()这系列函数内部实现逻辑大概是如下这样:
通过willChangeValueForKey与didChangeValueForKey的调用,可以实现手动触发KVO监听。
所以可以大概知道这个函数内部实现应该是如下:
在didChangeValueForKey:里面实现了内部调用属性观察者observer的observeValueForKeyPath:ofObject:change:context:方法。
4、通过KVC设值age会不会触发KVO监听吗?
结果可以发现,KVC也会触发KVO监听。其内部也是实现了willChangeValueForKey与didChangeValueForKey的调用。
二、KVC设值取值原理流程图
常用API使用:
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
- (void)setValue:(id)value forKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;
- (id)valueForKey:(NSString *)key;
1、设值原理
说明:+(BOOL)accessInstanceVariablesDirectly方法默认返回值YES;
2)取值原理
对于KVC的设值取值的顺序,可以自己建一个类把对应方法写进去看下执行及顺序。
三、总结
0、利用addObserver:forKeyPath...函数增加观察值变化,内部实现会利用Runtime动态生成一个NSKVONotifying_HPPerson类(HPPerson的子类)。NSKVONotifying_HPPerson类中也有setAge方法,实现代码是直接调用了Foundation下的_NSSet***ValueAndNotify()系里函数。
1、_NSSet***ValueAndNotify()系里函数实现逻辑为调用willChangeValueForKey与didChangeValueForKey方法来实现。
2、-(void)didChangeValueForKey方法内部实现[observer observeValueForKeyPath: ofObject...]的调用。
3、KVO需要通过set方法来实现键值监听,如果直接赋值成员变量是不会触发KVO。
4、KVO可以通过手动调用-(void)willChangeValueForKey与-(void)didChangeValueForKey方法来实现。
5、KVC设值会触发KVO。