KVO 是怎么实现的

首先直接引用孙源的 objc kvo简单探索中已经得出的结论。

KVO的原理
简而言之就是:

  1. 当一个object有观察者时,动态创建这个object的类的子类。
  2. 对于每个被观察的property,重写其set方法。
  3. 在重写的set方法中调用- willChangeValueForKey:和- didChangeValueForKey:通知观察者。
  4. 当一个property没有观察者时,删除重写的方法
    当没有observer观察任何一个property时,删除动态创建的子类。

在1、2之间还应补充一条: 改变object的类别为动态创建的子类。

如何实现

1.

当一个object有观察者时,动态创建这个object的类的子类。

通过以下方法可以动态创建一个类。

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Creates a new class and metaclass.
*
* @param superclass The class to use as the new class's superclass, or \c Nil to create a new root class.
* @param name The string to use as the new class's name. The string will be copied.
* @param extraBytes The number of bytes to allocate for indexed ivars at the end of
* the class and metaclass objects. This should usually be \c 0.
*
* @return The new class, or Nil if the class could not be created (for example, the desired name is already in use).
*/
Class objc_allocateClassPair(Class superclass, const char *name,
size_t extraBytes)

根据方法注释中的note:

When you are done building the class, call objc_registerClassPair. The new class is now ready for use. The new class is now ready for use.

我们知道还需要向runtime注册刚才动态生成的类。

1
2
3
4
5
6
7
/**
* Registers a class that was allocated using \c objc_allocateClassPair.
*
* @param cls The class you want to register.
*/
void objc_registerClassPair(Class cls)

2.

改变object的类别为动态创建的子类。

1
2
3
4
5
6
7
8
9
10
/**
* Sets the class of an object.
*
* @param obj The object to modify.
* @param cls A class object.
*
* @return The previous value of \e object's class, or \c Nil if \e object is \c nil.
*/
Class object_setClass(id obj, Class cls)

通过此方法便能修改 obj 的类别。

3、4

对于每个被观察的property,重写其set方法。
在重写的set方法中调用- willChangeValueForKey:和- didChangeValueForKey:通知观察者。

首先要知道一点,子类里面是没有实现 set 方法的,当子类执行父类的方法时,是怎么查找到父类的方法的,这个以后再讲,现在我们可以根据这个来判断是否需要添加 set 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (BOOL)_hasSelector:(SEL)selector {
Class clazz = object_getClass(self);
unsigned int methodCount = 0;
Method* methodList = class_copyMethodList(clazz, &methodCount);
for (unsigned int i = 0; i < methodCount; i++) {
SEL thisSelector = method_getName(methodList[i]);
if (thisSelector == selector) {
free(methodList);
return YES;
}
}
free(methodList);
return NO;
}

如果没有 set 方法,给子类添加 set 方法

1
2
3
4
if (![self _hasSelector: setterSEL]) {
Method setterMethod = class_getInstanceMethod(originClass, setterSEL);
class_addMethod(object_getClass(self), setterSEL, (IMP)_vis_kvo_setter, method_getTypeEncoding(setterMethod));
}

set 方法里面是怎么实现的,篇幅有限,你可以在底下下载源码去看。反正不是通过实现- willChangeValueForKey:和- didChangeValueForKey:

5.

当一个property没有观察者时,删除重写的方法
当没有observer观察任何一个property时,删除动态创建的子类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (void)vis_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath {
if (不是动态创建的子类 && 是动态创建的子类但是没有实现 set 方法) {
return;
}
if (property没有观察者) {
NSString * originClassName = [kvoClassName componentsSeparatedByString:kVisKVOClassPrefix].lastObject;
Class originClass = NSClassFromString(originClassName);
// 恢复到之前的class
object_setClass(self, originClass);
// 注销动态创建的类
objc_disposeClassPair(kvoClass);
}
}

这里用伪代码写的,因为我没找到删除类的实例方法的API,所以我用的是objc_setAssociatedObjectobjc_getAssociatedObject创建的NSArray <NSDictionary *> *数组来管理所有的观察者以及它们观察的 keyPath等等。

Demo

VisKVO-KVC (顺便把 KVC 也实现了…)

参考文献

objc kvo简单探索 - sunnyxx

如何自己动手实现 KVO - okcomp

谢谢

欢迎勘误,共同进步。