最近在看kvo的原理,网上有很多前篇一律的文章,当然也有几篇写的非常好的。
比如https://www.jianshu.com/p/cf079e5433e4和https://www.jianshu.com/p/badf5cac0130
以下是自己的一些理解:
什么是KVO?
KVO是苹果的观察者模式的一个实现。一个对象可以添加监听者来监听自己的一些状态变化。
比如:
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
person对象添加了监听者self。当person对象的属性发生变化的时候,'self'
就可以拿到这个变化。
怎么实现的?
在了解kvo的原理之前,我们先思考一下,如果是我们来做这件事,应该怎么做?
我觉得应该有以下几步:
- person对象内部维护一个数组或者表,用来存放监听者和监听属性
- 重写person对象的setter方法,当setter方法传过来的参数和之前的信息不一样的时候,我们就想办法通知到监听者。
我觉得上面的思路应该很好实现,因为难点不多。
但是对比系统的做法,感觉代码上不够简洁和高大上。
系统的做法是:
-
当给一个对象添加监听者的时候,会动态生成一个新的类,类名是
NSKVONotifying_+当前类名
-
将当前对象的isa指针指向这个新生成的子类
为什么要改isa指针?因为我们用对象调用方法的时候,是通过对象的isa指针找到对象所属的类,然后在类的方法列表里找方法。所以如果要调用新生成的子类的setter方法,就需要更改这个。 -
重写当前对象的class方法重写,使其看起来仍然是之前的类
-
当对象的属性发生变化的时候。新生成的子类setter方法开始调用
- 修改之前调动
willChangeValueForKey:
- 调用父类的setter方法
- 修改之后调用
didChangeValueForKey:
- 修改之前调动
-
didChangeValueForKey:
调用后,就会发送通知。监听者的observeValueForKeyPath:ofObject:change:context:
方法将会调用
看到这里,不知道大家会不会和我一样有一个疑惑。未实现就会crash。但是为什么不加方法实现的判断?如果加了不就可以预防crash?
后来仔细想了想,因为observeValueForKeyPath:ofObject:change:context:
是非正式协议。基本上可以理解为是NSObject系所固有的方法,任何该系列的类都有这个方法。本身系统对方法实现与否都不关心,如果真的要对这个方法加预防处理,那么其实所有的方法都应该加预防。这样就不会有后面的消息转发机制了。
所以这样一想,也就能理解为什么系统不加预防判断了。 这些东西其实就是开发者自己应该关注的问题,而不是系统。
还有一点想要分享
如果自己想手动实现kvo,不光光是要重写setter方法,加上willChangeValueForKey:
和didChangeValueForKey:
。
如果只是这样写了,会发现监听的方法将被调用两次。
调用两次的原因是,系统本身会触发kvo一次,然后我们手动也触发了一次。
所以应该实现+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key;
方法,对于我们要手动触发的kvo,返回NO
网友评论