KVO 就是 Key-Value Observing,就是键值观察。它是一种观察者模式的衍生。其基本思想是,对目标对象的某属性添加观察,当该属性发生变化时,通过触发观察者对象实现的KVO接口方法,来自动的通知观察者。
以下代码为p对象的name属性添加了KVO观察。然后在添加观察前下个断点。
通过lldb调试可以看到,在执行addObserver方法之前,p对象Class类型为LGPerson。
好,在执行完addObserve这行代码之后,p对象Class类型就变成了
NSKVONotifying_LGPerson。
那这个NSKVONotifying_LGPerson和LGPerson有什么关系呢?
或者说这个NSKVONotifying_LGPerson是本来就存在的还是动态生成的呢?
没有关系,我们来测试一下。
然后再27行代码出下断点调试。
这就说明,这个NSKVONotifying_LGPerson并不是一开始存在,而是通过addObserve这行代码动态生成的。
NSKVONotifying_LGPerson是LGPerson的子类。
在移除观察者代码处下俩个断点。
通过打印信息我们可以发现addObserve的时候,self.p的isa指针指向了
NSKVONotifying_LGPerson,在removeObserver之后,self.p的isa指针又指回了
LGPerson。
NSKVONotifying_LGPerson类会自动生成几个方法,如下图所示。
关键是这个setName方法。实现功能依赖的就是这个setName。
那这个setName方法究竟做了啥呢?
我们首先下俩个断点。在26行代码处lldb输入watchpoint set variable self->_p->_name。
watchpoint set variable观察某个值是否修改
然后过掉26行代码断点。我们28行代码处给self.p.name赋值的时候,代码就被断住了。
大家可以看到,在给self.p.name赋值之前,也就是调用p的setName方法之前,系统还调用了下面这俩个方法。
这个方法里面有NSKeyValueWillChange和NSKeyValueDidChange。
这也是为什么观察成员变量变化不生效的原因。因为并不会生成setter方法
那为什么成员变量用setValue:forKey的方式就能观察到,产生相应的回调呢。
这个需要用到GNUstep的源码,虽然GNUstep不是苹果官方的源码,但是还是有很高的参考价值的
在GNUstep搜索observeValueForKeyPath
表示被观察对象收到了一个 -setValue:forKey:消息,或者键值编码兼容的设置方法键已被调用,
或者 -willChangeValueForKey:
或-didChangeValueForKey:对已被调用。
但是需要注意一点:willChangeValueForKey和didChangeValueForKey一定要成对出现。
那observeValueForKeyPath是什么时候调用的呢?
是通过notifyForKey方法。
在didChangeValueForKey方法里面就会调用notifyForKey方法来触发
observeValueForKeyPath的回调。
所以,KVO的实现原理为:在我们调用addObserve的时候,会动态生成一个以NSKVONotifying_开头的当前类的一个子类,对象的isa指针就会指向这个类,系统会自动生成相应的方法。
这个系统生成的子类回去调用对应的setter方法,在这个setter方法的内部,其实就是调用
willChangeValueForKey和didChangeValueForKey这俩个方法。
然后在didChangeValueForKey方法内部会调用notifyForKey方法来触发
observeValueForKeyPath的回调。
- END -
本文分享自 HelloCoder全栈小集 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!