之前章节
01.OC实例对象的本质
02.OC有几种对象
03.对象的isa与superclass
网上很多文章讲解KVO的实现原理,但没过多久可能就忘了,这篇文章带你从苹果开发者的角度来实现KVO监听的技术,假设你现在是一名苹果开发人员,没错,我们现在就是一名苹果开发人员,遇到了一个问题,如下图

当age改变的时候,希望能调用personAgeChanged方法,这时我们会想要怎么去实现该功能呢?
最简单的,我们可以使用代理或通知模式来告诉外面,代理的话,假设有几个属性要监听的就要写很多个代理方法,太麻烦了,这里我们采用通知订阅者模式,那么通知模式通常有个通知管理中心,我们创建一个通知中心类,叫KZKVONotifictionCenter,KZ是个人前缀可忽略,如下图:

分别有添加监听、移除监听、即将改变值、已经改变值方法,但如果我们要使用这个监听中心,其实也挺麻烦的,能否更加面向对象一点呢,这时我们就可以考虑使用oc的分类方法,同样创建一个NSObject+KVO分类,它的代码如下:

有了分类方法,那么我们就可以这样子调用

哇,真的方便多了,那么我们的分类方法里面实现是怎样的呢?让我们来看看

大家可以看到,添加监听向监听中心添加,然后把自己的isa指向自己的子类KZKVONotifying_Person,为啥要指向自己的子类,我们先来看看子类KZKVONotifying_Person的代码

原来在子类中,重写了setAge,可以看到子类的setAge分别调用了kz_willChangeValueForKey和kz_didChangeValueForKey,这俩个方法,其实是让监听中心记录下改变前和改变后的值,存储起来,然后告知监听者

如果我们的监听者如果有实现kz_observeValueForKeyPath…这个方法,那么就可以监听到age改变时的通知了,如下图完整调用代码

是不是很简单,这个模式主要是针对键值的监听,key-value-observer,那么我们就叫它KVO吧~
上面模拟了苹果对KVO的简单粗暴版实现,那么我们如何证明系统也是这样子实现的呢,首先证明的是调用addObserver这个方法后,isa指向改变的问题,可以看下图,指向前和指向后的isa


这里可能你会奇怪,KVONotifying_Person这个类没定义是从哪里来的,答案是苹果利用运行时,动态创建子类,继承了Person,这样子更加方便开发,神不知鬼不觉的实现了该技术,不用我们自己去创建子类实现。至于更换isa有啥用,我们在前面的章节讲过,isa指向了子类后,会去查找子类的方法列表,子类重写了对age的监听,这时调用了setAge,其实isa会去KVONotifying_Person调用setAge,如图

细节补充
- 子类的set方法最终会调用_NSSet类型ValueAndNotify函数,如_NSSetIntValueAndNotify,为什么要使用该函数呢?本人的想法是函数里面调用了xxxxx代码,都是同一份,所以不需要每次都动态生成
- 既然isa指向改了,为什么使用[Person class]方法还是返回Person,不应该返回KVONotifying_Person么,答案是KVONotifying_Person也重写了class方法,返回了Person,原因是不想让开发关注太多东西,不需要知道是怎么实现的,不止class,还重写了dealloc和_isKVOA等方法
- 直接修改成员变量不会触发KVO,只有经过set方法的才会触发,KVC经过set方法,会触发
- 记得移除kvo,有没有不用移除更好的方式?可以去搜一下KVOController库看看
本文部分知识来自李明杰的教程,感谢观看~
网友评论