美文网首页iOS 底层原理探究
KVO(二)探究KVO的本质

KVO(二)探究KVO的本质

作者: PerryMorning | 来源:发表于2019-11-10 12:29 被阅读0次

    上篇文章我们通过一个简单的例子,讲述了KVO的基本使用情况,下面我们来继续深究KVO的本质是什么。

    想要探究本质,就要和没有使用KVO的对象来进行对比,对比法是最容易看出不同的。

    (一)添加观察者对象以后,系统帮我们做了什么操作

    Person类只有一个属性age:

    我们创建Person类的两个对象person1 和 person2,person1通过KVO观察age属性的变化,person2不做特殊处理。我们来对比一下person1 和 person2 的类对象和元类对象是否一样。

    得到如下结果:

    通过观察结果,我们得知:

      1、  在向person1对象添加观察者之前,person1 和 person2 的类对象都是指向PMPerson,没有任何区别;

       2、 当向person1对象添加观察者之后,person1的类对象变成了NSKVONotifying_PMPerson,person2没有变化;

        3、同时我们继续观察person1和person2所指向的元类对象也不一样,person1的元类对象是NSKVONotifying_PMPerson,person2的元类对象仍旧是PMPerson。

    进一步分析,也就是在添加观察者之后,系统对被观察者重新生成了一个中间类对象和对应的元类,类名以NSKVONotifying_开头。

    (二)那么以NSKVONotifying_PMPerson类和PMPerson类之间究竟有什么关系呢?

    我们利用在NSObject中学习到的isa指针和superClass指针来验证一下:

    不熟悉的朋友可以看这篇文章:isa 与 superClass

    注意观察结果:pm_person1Class是经我们转换过的person11Class对象,pm_person1MetaClass是经过转换后的meta1Class对象,便于通过superClass指针访问。

    当我们使用superClass指针查看具体指向地址时,发现pm_person1MetaClass->superclass指向PMPerson的地址,由于pm_person1Class是NSKVONotifying_PMPerson类型,也就是说NSKVONotifying_PMPerson是继承自PMPerson类。

    同样的道理,我们可以得到NSKVONotifying_PMPerson元类也是继承自PMPerson元类。

    通过以上证明,我们得知,系统生成的NSKVONotifying_XXX类,其实是继承自原来的XXX类。

    (三)探究NSKVONotifying_XXX又做了什么操作?

    想知道NSKVONotifying_XXX类做了什么操作,就得知道NSKVONotifying_XXX里面究竟有什么方法,我们利用runtime机制来探究一下。通过以下方法,打印NSKVONotifying_XXX的方法列表:

    我们分别传递NSKVONotifying_PMPerson的类对象和元类对象,看里面究竟有什么方法:

    结果如下:

    也就是说在NSKVONotifying_PMPerson中,实现了四个实例方法,分别是:

    setAge:(),class(),dealloc()和_isKOVA。

    dealloc()应该是做一些收尾工作,_isKOVA应该是返回一个BOOL值,在这里应该是返回一个YES。

    我们把重点放在setAge:和 class()方法中。

    首先我们看一下class()方法,通过之前我们对NSObject的了解,class()能够得到类对象,我们看一下调用NSKVONotifying_PMPerson 的 class() 方法会得到什么结果。

    结果如下:

    不难看出通过runtime获取到的是真正的类名,而Class方法获取到的是其父类名。

    由此可以猜想在NSKVONotifying_PMPerson中重写class()是为了隐藏其真正实现,毕竟NSKVONotifying_PMPerson类是在编译过程中,系统利用runtime机制帮我们生成的,为了打消我们的疑虑,当我们调用class()时,就直接返回父类的方法,其内部实现有可能是这样子的:

    最后只剩setAge:()了,这也是实现KVO的关键机制。当我们点击屏幕,触发KVO时,看一下系统的调用栈,可以通过工具栏直接查看,或者在lldb输入bt命令进行查看,得到如下结果:

    得到两个关键信息:

        1.调用Foundation框架的_NSSetLongLongValueAndNotify方法

        2.调用:NSKeyValueDidChange方法。

    由于我们无法看到Foundation的具体实现,我们猜想在NSKVONotifying_PMPerson中的setAge:()方法调用了_NSSetLongLongValueAndNotify,然后在_NSSetLongLongValueAndNotify()中会进行真正的赋值操作,最终触发KVO,并调用NSKeyValueDidChange。

    由于NSKVONotifying_PMPerson是继承自PMPerson,那么在PMPerson中肯定能够得到验证。

    有NSKeyValueDidChange(),肯定就会有NSKeyValueWillChange()

    接着我们来验证一下这个猜测,在PMPerson中实现以下方法:

    点击屏幕,触发KVO:

    看打印结果非常清晰,实际的调用顺序:

    1、NSKVONotifying_PMPerson调用setAge:();

    2、setAge()调用_NSSetLongLongValueAndNotify;

    3、_NSSetLongLongValueAndNotify 调用willChangeValueForKey();

    4、调用PMPerson的setAge:(),改变属性的值;

    5、调用didChangeValueForKey();

    6、调用NSKeyValueNotifyObserver();

    7、didChangeValueForKey()调用结束。

    到这里关于NSKVONotifying_PMPerson中setAge()的剖析已经基本清楚。

    可能大家还会有一个疑问,为什么会调用_NSSetLongLongValueAndNotify呢?其实跟我们需要观察的属性类型有关系。我们声明的age属性时NSInteger类型,即:

    在我们的调试环境中,NSInteger就是long类型,假如我们需要观察的变量是int类型,在NSKVONotifying_XXX类的set方法中调用的自然就是_NSSetIntValueAndNotify方法了,感兴趣的朋友,可以自己试一下。

    总结:

        1、当我们给某一个对象添加观察者观察特定属性时,系统通过runtime生成了一个中间类,NSKVONotifying_XXX;

        2、NSKVONotifying_XXX通过重写被观察属性的set方法,达到通知观察者的目的。

    相关文章

      网友评论

        本文标题:KVO(二)探究KVO的本质

        本文链接:https://www.haomeiwen.com/subject/hahobctx.html