KVO相信iOS开发者们都听说过,在面试中也会被常常问到,但是呢对于KVO来说更多的事情是由系统来做的,依赖于运行时,相对于Notification,delegate来说是比较简单的,提供观察属性旧值与新值,以下单纯的说下自己对KVO的实现原理粗略理解,用Runtime简单模拟实现KVO。

目录

  1. KVO是什么
  2. 实现原理
    1. 基本的原理:
    2. 深入剖析:
  3. 回调相关问题
  4. 最后

KVO是什么

KVO 是 Objective-C 对观察者模式(Observer Pattern)的实现。也是 Cocoa Binding 的基础。当被观察对象的某个属性发生更改时,观察者对象会获得通知。

实现原理

KVO 在 Apple 中的 API 文档如下:

Automatic key-value observing is implemented using a technique called isa-swizzling… 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 …

KVO 的实现依赖于 Objective-C 强大的 Runtime,从以上 Apple 的文档可以看出苹果对于 KVO 机制的实现是一笔带过,而具体的细节没有过多的描述,但是我们可以通过 Runtime 的所提供的方法去探索关于KVO 机制的底层实现原理。

基本的原理:

KVO 是基于 runtime 运行时来实现的,当你观察了某个对象的属性,内部会生成一个该对象所属类的子类,然后从写被观察属性的setter方法,当然在重写的方法中会调用父类的setter方法从而不会影响框架使用者的逻辑,之后会将该对象的isa指针指向新创建的这个类,最后会重写-(Class)class;方法,让使用者通过[obj class]查看当前对象所属类的时候会返回其父类,达到移花接木的目的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
NSLog(@"class-withOutKVO: %@ \n", object_getClass(self.testObj));
NSLog(@"setterAdress-withOutKVO: %p \n", [self.testObj methodForSelector:@selector(setName:)]);
[self.testObj addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context: nil];
[self.testObj addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context: nil];
self.testObj.name = @"xxxx";
self.testObj.age = 12;
NSLog(@"class-addKVO: %@ \n", object_getClass(self.testObj));
NSLog(@"setterAdress-addKVO: %p \n", [self.testObj methodForSelector:@selector(setName:)]);
[self.testObj removeObserver:self forKeyPath:@"name"];
[self.testObj removeObserver:self forKeyPath:@"age"];
NSLog(@"class-removeKVO: %@", object_getClass(self.testObj));
NSLog(@"setterAdress-removeKVO: %p \n", [self.testObj methodForSelector:@selector(setName:)]);

看到了么,我们使用object_getClass ()方法成功躲开了 KVO 的障眼法,发现添加观察过后,_obj的类变成了NSKVONotifying_TestObj,在移除观察过后,_obj的类又变回TestObj。同时,我们还观察了setAName:方法的地址,发现同样是有变化,同样验证了重写setter方法的逻辑。

深入剖析:

关键代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
- (BOOL)wxd_coreLogicWithTarget:(id)target getterName:(NSString *)getterName {
//若 setter 不存在,根据属性名(也是getter的方法名)生成setter的方法名
NSString *setterName = setterNameFromGetterName(getterName);
SEL setterSel = NSSelectorFromString(setterName);
Method setterMethod = class_getInstanceMethod(object_getClass(target), setterSel);
if (!setterMethod) return NO;
//创建派生类并且更改 isa 指针
[self wxd_creatSubClassWithTarget:target];
//给派生类添加 setter 方法体
if (!classHasSel(object_getClass(target), setterSel)) {
const char *types = method_getTypeEncoding(setterMethod);
return class_addMethod(object_getClass(target), setterSel, (IMP)wxd_kvo_setter, types);
}
return YES;
}
//创建派生类并且更改 isa 指针
- (void)wxd_creatSubClassWithTarget:(id)target {
//获取被观察者的类
Class nowClass = object_getClass(target);
NSString *nowClass_name = NSStringFromClass(nowClass);
//如果isa指针已经指向子类返回
if ([nowClass_name hasPrefix:kPrefixOfWXDKVO]) {
return;
}
//动态生成新的子类类名
NSString *subClass_name = [kPrefixOfWXDKVO stringByAppendingString:nowClass_name];
Class subClass = NSClassFromString(subClass_name);
//若派生类存在
if (subClass) {
//将该对象 isa 指针指向派生类
object_setClass(target, subClass);
return;
}
//添加派生类,并且给派生类添加 class 方法体,其中nowClass是传进来的目标类
subClass = objc_allocateClassPair(nowClass, subClass_name.UTF8String, 0);
const char *types = method_getTypeEncoding(class_getInstanceMethod(nowClass, @selector(class)));
IMP class_imp = imp_implementationWithBlock(^Class (id target){
return class_getSuperclass(object_getClass(target));
});
class_addMethod(subClass, @selector(class), class_imp, types);
//注册该类
objc_registerClassPair(subClass);
//将该对象 isa 指针指向派生类
object_setClass(target, subClass);
}

回调相关问题

接下来就是回调的情况了,在重写的setter里面逻辑是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static void wxd_kvo_setter (id taget, SEL sel, id p0) {
//拿到调用父类方法之前的值
NSString *getterName = getterNameFromSetterName(NSStringFromSelector(sel));
id old = [taget valueForKey:getterName];
callBack(taget, nil, old, getterName, YES);
//给父类发送消息
struct objc_super sup = {
.receiver = taget,
.super_class = class_getSuperclass(object_getClass(taget))
};
((void(*)(struct objc_super *, SEL, id)) objc_msgSendSuper)(&sup, sel, p0);
callBack(taget, p0, old, getterName, NO);
}

最后

以上介绍了 KVO 的实现原理并自己实现了一个简单的 KVO,源码 KVO Demo,感兴趣的可以下载下来看看。
实际上 KVO 的实现还是很复杂的,要考虑到很多地方,复杂的实现网上有相关代码,或者看 KVO 源码了解一下。