官方文档解释
苹果官方文档上有KVO实现的一段话,很“言简意赅”
Automatic key-value observing is implemented using a technique called isa-swizzling.
Theisapointer, as the name suggests, points to the object's class which maintains a dispatch table. This dispatch table essentially contains pointers to the methods the class implements, among other data.
When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class. As a result the value of the isa pointer does not necessarily reflect the actual class of the instance.
You should never rely on theisapointer to determine class membership. Instead, you should use theclassmethod to determine the class of an object instance.
翻译过来的意思就是KVO使用一种叫做 isa-混写技术,当观察者被注册为所观察到的物体的isa指针被修改的对象的属性,指向一个中间类,而不是在真类实现。
!!!!
。。。。
WTF?!
先看一下Class的定义
typedef struct objc_class *Class;
typedef struct objc_object {
Class isa;
} *id;
isa 其实就是一个objc_class的指针,用来指向类的类型,我们可以通过object_getClass方法来获取这个值。正常来说,class方法内部的实现就是获取这个isa指针,但是在kvo中苹果对监听对象的这个方法进行了重写。
在oc中,规定了只要拥有isa指针的变量,通通都属于对象。上面的objc_object表示的是NSObject这个类的结构体表示,因此oc不允许出现非NSObject子类的对象(block是一个特殊的例外)
在添加监听者之前和之后,分别打印出两个UITabelView的内存地址、类名称、object_getClass方法得到的类:
之前:
address: 0x7f927a81d200
class method: UITableView
description method:UITableView
之后:
address: 0x7f927a81d200
class method: UITableView
description method:NSKVONotifying_UITableView
发现objective_getClass调用的方法证明两个类确实是不同的类,类名称一样可能是因为苹果重写了class方法,但是为什么内存地址一样呢?
如果打印NSLog(@"%@, %@", self.class,super.class)可以发现,不管你怎么输出,self.class和super.class永远都指向同一内存区域。
每一个对象占用的内存中,一部分是父类属性占用的;在父类占用的内存中,又有一部分是父类的父类占用的。前文已经说过isa指针指向的是父类,因此在这个图中,Son的地址从Father开始,Father的地址从NSObject开始,这三个对象内存的地址都是一样的。通过这个,我们可以猜到苹果文档中所提及的中间类就是被监听对象的子类。并且为了隐藏实现,苹果还重写了这个子类的class方法跟description方法来掩人耳目。另外,我们还看到了新类相对于父类添加了一个NSKVONotifying_前缀,添加这个前缀是为了避免多次创建监听子类,节省资源。