前言
iOS开发中,有一种设计模式应用广泛,那就是观察者模式。苹果称其为 KVO(Key-Value Observing),既键值观察,总是有人把 KVC 和 KVO 混为一谈,实则它们只是名字长得像。相信看完本篇博客,并且看明白 github 中本人对其的代码实现,可以把 KVO 理解得更深刻。
KVO基于runtime实现代码 [1]
当然要实现它,我们就得先了解它,所以接下来会讲解 KVO 的用法以及简单原理。
一、KVO 用法
虽然用法很基础,还是简单提一下。
添加观察
[_obj addObserver:self forKeyPath:@"aName" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:(__bridge void *)(self)];
方法的调用者(就是这个_obj)就是被观察的对象。
observer参数是回调的接收者。
keyPath是一个寻找路径,最终落脚点是一个有效的可观察属性。
options有几个配置回调可选项,NSKeyValueObservingOptionOld表示获取旧值,NSKeyValueObservingOptionNew表示获取新值,NSKeyValueObservingOptionInitial表示在添加观察的时候就立马响应一个回调,NSKeyValueObservingOptionPrior表示在被观察属性变化前后都回调一次。
context是一个指针,用来准确匹配添加观察和接收观察,主要是在特定情况下无法区分该观察回调是否需要处理进行精确判断。
获取回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ((__bridge id)context == self) {
NSLog(@"keyPath: %@, object: %@, change: %@, context: %@", keyPath, object, change, context);
}
}
变换一个属性的值,打印如下:
keyPath: aName, object: , change: {
kind = 1;
new = jack;
old = "";
}, context:
change字典里面的new和old就是我们需要的值了,kind是关键路径属性的类型标识,具体可以去看api。
移除观察
[_obj removeObserver:self forKeyPath:@"aName"];
移除观察很简单,和移除通知比较类似,我们需要在不用继续观察的时候移除它,比如在控制器的dealloc方法里面释放,值得注意的是重复移除会 crash。
KVO 设计的槽点
其实作为开发者,大家应该经常听到对 KVO 的吐槽:
回调方式单一。
keypath设计容易写错。
KVO 的回调有一个传递链,子类若不调用父类方法