KVO与KVC的实现原理

一、KVO的实现原理

KVO的全称是Key-ValueObserving(键值监听),可以用于监听某个对象属性值的改变。

1、KVO的使用API如下


KVO与KVC的实现原理_第1张图片

通过addObserver: forKeyPath: options: context:添加观察者对某个属性的监听。

2、大家看下我写的代码,在设置age打个断点来调试。


KVO与KVC的实现原理_第2张图片

为什么会这样?明明我的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与KVC的实现原理_第3张图片

由此可以看出添加KVO后的实例对象的类对象变成了NSKVONotifying_HPPerson类,而没有添加KVO的实例对象的类对象还是之前的HPPerson类。

总结关系如下图:

1)添加KVO的类关系图谱


KVO与KVC的实现原理_第4张图片

2)没有添加KVO的类关系图谱


KVO与KVC的实现原理_第5张图片

3)如果增加一个stirng类型的name属性,则会调用_NSSetCharValueAndNotify(),由此可以知道_NSSet**ValueAndNotify()有非常多个这个函数,根据不同类型属性调用不同函数名的函数,这个属于Foundation框架下的函数,可以通过越狱设备拿到Foundation的二进制文件,然后反编译后去查找这类函数的实现。过程太复杂,这里不深入写下去了。 在这里只要知道我们的KVO实质是利用了Runtime生成了一个子类,在子类的setAge方法中调用_NSSetIntValueAndNotify()来实现的。

4)_NSSetIntValueAndNotify()这系列函数内部实现逻辑大概是如下这样:

通过willChangeValueForKey与didChangeValueForKey的调用,可以实现手动触发KVO监听。

所以可以大概知道这个函数内部实现应该是如下:


KVO与KVC的实现原理_第6张图片

在didChangeValueForKey:里面实现了内部调用属性观察者observer的observeValueForKeyPath:ofObject:change:context:方法。

4、通过KVC设值age会不会触发KVO监听吗?


KVO与KVC的实现原理_第7张图片

结果可以发现,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、设值原理

KVO与KVC的实现原理_第8张图片

说明:+(BOOL)accessInstanceVariablesDirectly方法默认返回值YES;

2)取值原理


KVO与KVC的实现原理_第9张图片

对于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。

你可能感兴趣的:(KVO与KVC的实现原理)