KVO原理探究
kvo是在开发中经常要使用的开发技术,可以用来观察实例对象中的属性值的变化。那么究竟系统是如何实现的呢?
首先我们来创建一个用来测试的类:
(1) 使用objc_copyClassList(objc/runtme.h)来获取所有已注册类定义的指针列表,通过注册观察者前后的注册类的变化来初步推断观察者的实现原理.
输出的结果:
通过观察者注册前后对比,我们发现在观察者注册之后,系统注册的类列表里多出了一个类NSKVONotifying_Human.
使用LLBD调试,我们发现,在观察者注册之前实例对象obj的isa(指向创建对象的类)指针指向的Human这个类,而观察者注册之后,实例对象obj的isa却指向了NSKVONotifying_Human.
(2)使用object_getClass(objc/runtime.h)和class_getSuperclass(objc/runtime.h)来探索观察者注册前后类的元类和父类的变化.
object_getClass该方法获取的是当前对象isa的指向:所以如果入参是实例对象时,返回的是创建对象的类;如果入参是Class对象,返回的是该对象的元类。
输出结果:
1.每个类都有一个对应的元类,该类和其对应的元类名字相同;
2.观察者注册之前Human的父类指向NSObject;观察者注册注册之后,继承链中多出了一个中间类NSKVONotifying_Human,同时Human的父类指向了NSKVONotifying_Human,而NSKVONotifying_Human的父类指向了NSObject.
(3)探究NSKVONotifying_Human中的属性和方法
3.1 分别使用class_copyIvarList和class_copyPropertyList来查看NSKVONotifying_Human中的成员变量和属性,查看一下该类有没有新增的实例方法.
kvo生成的子类中并没有添加额外的成员变量和属性.
3.2 使用class_copyMethodList获取对象的实例方法列表,查看一下该类有没有增加的实例方法.
kvo生成的子类中增加了四个实例方法:
- setName: 返回类型为void,参数为id类型(v表示返回值类型为void;24表示整个方法参数占位的总长度;@0表示在offset为0的地方有一个objective-c的对象,在OC中第一个参数即为self本身;:8表示在offset为8的地方有一个SEL;@16表示在offset为16的地方有一个id类型的参数,即传递进来的参数)
- class: 没有参数,返回类型为#,表示Class类型,其实是重写了class的getter方法(猜测是返回了父类的class方法实现);
- dealloc: 没有类型,返回参数为void,即重写了dealloc的实现;
- _isKVOA: 没有参数,返回类型的B,表示BOOL类型.
_isKVOA和class
- _isKVOA:是类是否添加观察者的一个标志,用以标志该类是否添加了观察者;
- class: 我们发现这个getter方法返回的类型是Human这个类,而不是NSKVONotifying_Human这个类。主要目的apple不希望暴露这个中间类,所以重写了class方法,将系统添加的这个中间类"隐身",不至于在使用过程中出现不必要的混淆,同时很好地将系统的这个类进行隐藏.
setName:
使用了kvo之后,setName:实现被替换了成了 Foundation`_NSSetObjectValueAndNotify,根据这个方法的名称我们大概猜测出该方法重新赋值并对外界发出通知.当该属性改时,回调用observeValueForKeyPath: ofObject: change: context:方法,来通知外部作出相应的处理.
3.3 将3.2中我们的方法稍做修改就可以查看NSKVONotifying_Human类方法是否有改变,
NSKVONotifying_Human这个中间类的类方法并没有增加.
3.4 通过注册观察者(observe)来监测属性变化系统是不是一定会创建中间类?
使用class_getSuperclass来递归查找当前类的父类,从而获得该类的继承链.
在默认情况下:
默认情况下,继承链是关系是NSKVONotifying_Human-->Human-->NSObject.
如果我们在Human中实现automaticallyNotifiesObserversForKey:将对应的key返回false,或者实现automaticallyNotifiesObserversOfName返回false,继承关系会不会发生变化呢?
- 实现automaticallyNotifiesObserversForKey:且将对应的key返回值修改为false;
- 实现automaticallyNotifiesObserversOfName并将返回值修改为false.
当上述条件满足其一时,我们发现,继承关系发生了变化Human-->NSObject,系统不再生成中间类.但是我们已经添加了观察者要怎么样才能开发手动观察者呢?
3.5 手动开启观察调用
其实根据我们上边方法的名字automaticallyNotifiesObserversForKey:我们可以大致猜的出来,既然我们阻止了automatically开启,那么一定有方法可以manual方式开启手动干预.
然后观察者又神奇开启了。
总结
根据上边的实践,我们大概可以得出如下结论:
- 我们使用kvo添加完观察者之后,系统会生成一个中间类,假设原始的类名为MyObject,类名为NSKVONotifying_MyObject;
- 该中间类增加了_isKVOA来标记该类已经添加了观察者;
- 该中间类重写了class的getter方法,返回了该中间类的父类,也就是未添加过观察者之前的创建对象的原始类,用来返回真实的原始类,并很好地隐藏了系统创建的这个中间类;
- 该中间类重写了setter方法,同时将setter方法的实现指向了_NSSetObjectValueAndNotify,在该方法里给属性赋值并调用了observeValueForKeyPath: ofObject: change: context:来通知外部属性发生变化并作出相应的处理;
- 该中间类重写了dealloc的,来做一些必要的处理.