什么是观察者模式?

观察者模式本质上时一种发布-订阅模型,我们先打个比方 比如你追星,你想知道你喜欢的爱豆的最新动态, 所以你会关注她的微博。 一旦她发了新微博,微博就会推送告诉你。 然后你就可以及时看到爱豆的微博动态。 这就是观察者模式,A对B的变化感兴趣,就注册为B的观察者,当B发生变化时通知A,告知B发生了变化。

观察者模式由三个角色组成:
观察者
被观察者
接收者
复制代码

当开始观察的时候,观察者就需要盯着被观察者,当被观察者发生变化的时候,都需要反馈消息给接收者。在 iOS 中,系统已经实现了 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: <TestObj: 0x604000435340>, change: {
kind = 1;
new = jack;
old = "<null>";
}, context: <KVOVC: 0x7fb06cd3cd10>
复制代码

change字典里面的new和old就是我们需要的值了,kind是关键路径属性的类型标识,具体可以去看api。 移除观察

[_obj removeObserver:self forKeyPath:@"aName"];
复制代码

移除观察很简单,和移除通知比较类似,我们需要在不用继续观察的时候移除它,比如在控制器的dealloc方法里面释放,值得注意的是重复移除会 crash。 KVO 设计的槽点

其实作为开发者,大家应该经常听到对 KVO 的吐槽:

回调方式单一。
keypath设计容易写错。
KVO 的回调有一个传递链,子类若不调用父类方法,传递链会中断,这个设计感觉有些繁琐。
多次移除同一 KVO 会 crash。
......
复制代码