KVO原理探秘

KVO的全称是Key-Value Observing,俗称“键值监听”,可以用于监听某个对象属性值的改变。

对于iOS开发者而言,相信大家都使用过KVO。但是对它的原理可能就不太清楚了。KVO是Runtime运行时机制的又一重大的应用,下面就对KVO的原理做一个详尽的阐述。

创建一个新的项目,然后在项目中创建一个ZPPerson类,这个类中有一个age属性。

KVO原理探秘_第1张图片
ZPPerson类
KVO原理探秘_第2张图片
age属性

然后在ViewController.m文件中创建一个person对象,并给它添加KVO监听。

KVO原理探秘_第3张图片
创建person对象
KVO原理探秘_第4张图片
给person对象添加KVO监听

当用户点击屏幕的时候,改变person对象的age属性的值。

KVO原理探秘_第5张图片
改变age属性的值

这个时候系统就会调用监听方法了。

KVO原理探秘_第6张图片
调用监听方法

下面阐述这个过程中KVO的实现原理:

  1. 在给这个person实例对象添加KVO监听之后,系统会利用Runtime机制动态地创建一个ZPPerson类的子类,名字就叫做"NSKVONotifying_ZPPerson"。然后系统会把这个person实例对象里面的isa指针由原来的指向ZPPerson类的class对象变为现在的指向NSKVONotifying_ZPPerson类的class对象;
  2. 新创建的子类"NSKVONotifying_ZPPerson"的class对象里面存储着isa指针、superclass指针、"setAge:"实例方法、"class"实例方法、"dealloc"实例方法以及"_isKVOA"实例方法等。其中系统会重写"setAge:"实例方法,重写后的该方法与它父类中的该方法的实现是不一样的,只不过方法的名称是一样的而已;
  3. 添加完KVO监听之后,当开发者调用"setAge:"实例方法来修改person对象的age属性的时候,根据上面所述,这个instance对象里面的isa指针已经由原来的指向ZPPerson类的class对象变为了现在的指向NSKVONotifying_ZPPerson类的class对象了,所以系统根据这个instance对象里面的isa指针找到的是NSKVONotifying_ZPPerson类的class对象,然后在这个class对象里面找到重写后的"setAge:"实例方法,最后再进行调用。这个重写的"setAge:"实例方法里面会调用Foundation框架里面的C语言函数"_NSSetIntValueAndNotify();",这个函数的实现里面首先会执行"[self willChangeValueForKey:@"age"];"代码,然后再执行"[super setAge:age];"代码,在执行这句代码的时候就会执行它的父类,也就是ZPPerson类里面的"setAge:"方法,从而真正更改这个属性的值,最后再执行"[self didChangeValueForKey:@"age"];"代码。"didChangeValueForKey:"这个方法里面会通知监听器"age"属性的值被改变了,即调用"- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context"监听方法,从而实现了对对象的某个属性进行监听的目的。

可以从下面的图中看出NSKVONotifying_ZPPerson类和ZPPerson类的关系:

KVO原理探秘_第7张图片
NSKVONotifying_ZPPerson类和ZPPerson类的关系

除了上述的改变对象的属性值的时候会自动调用KVO的监听方法之外,想要在不改变对象属性值的时候也能自动调用KVO的监听方法,应该怎么做呢?

手动触发KVO:

在点击屏幕的时候调用手动触发KVO的方法:


KVO原理探秘_第8张图片
调用手动触发KVO的方法

在方法中需要主动调用"willChangeValueForKey:"和 "didChangeValueForKey:"方法。

KVO原理探秘_第9张图片
手动触发KVO方法

这样的话,在点击屏幕的时候就会手动触发KVO的监听方法了"- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context"。

image.png

以上就是对KVO原理的阐述。

Github Demo

补充:

修改对象里面的成员变量的值是不能够触发KVO的监听方法的,代码如下所示:

在ZPPerson类里添加一个_age成员变量:

KVO原理探秘_第10张图片
_age成员变量

在ViewController.m文件中给age属性赋值:

KVO原理探秘_第11张图片
给age属性赋值

点击屏幕的时候修改_age成员变量的值:

KVO原理探秘_第12张图片
修改_age成员变量的值

运行之后可以知道通过这种方式改变对象的成员变量的值是无法自动触发KVO的监听方法的"- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context"。

修改对象的成员变量无法触发KVO监听方法的原因:

原因就在于对于已经被添加KVO监听的对象而言,instance对象的isa指针已经被改变为了指向系统新创建的子类的class对象了,然后在这个子类的class对象里面找到了(setAge:)实例方法,最后再进行调用。由之前的Demo可知,当调用新创建的子类的class对象中的属性的set实例方法的时候其实是在调用Foundation框架里面的C语言函数"_NSSetIntValueAndNotify",这个函数中会执行"didChangeValueForKey:"方法,此方法会触发KVO的监听方法。由此可知KVO的本质是调用set实例方法,即只有通过调用属性的set实例方法来修改属性的值才能触发KVO的监听方法,而修改对象的成员变量就没有调用属性的set方法,所以是不能够触发KVO的监听方法的。

如果想要在修改对象的成员变量的值的时候成功触发KVO的监听方法的话就要手动进行触发:

KVO原理探秘_第13张图片
image.png

在手动触发方法的实现里面要调用"willChangeValueForKey:"和 "didChangeValueForKey:"方法:

KVO原理探秘_第14张图片
image.png

运行之后就可以在控制台上看到成功调用了KVO的监听方法了:

KVO原理探秘_第15张图片
控制台打印结果

Github Demo

”三人行,必有我师焉“, 欢迎各位批评指正。
如果您还觉得我写的不错的话请您点赞加关注,您的肯定是我前进的最大动力!
我是爱学习也爱您的树懒O(∩_∩)O

你可能感兴趣的:(KVO原理探秘)