Key-value observing provides a mechanism that allows objects to be notified of changes to specific properties of other objects. It is particularly useful for communication between model and controller layers in an application.
kvo提供了一种机制,允许其他对象的特定属性的变化通知给目标。对于应用程序中模型层和控制层之间的通信特别有用
这是苹果的官方文档描述,我们可以端倪出 MVVM的一些气息
相信没有不会使用的
我们可以通过keypath做if 判断过滤
那么这个context是干嘛用的,怎么用?
让我们回到官方文档
The context pointer in the addObserver:forKeyPath:options:context:
message contains arbitrary data that will be passed back to the observer in the corresponding change notifications. You may specify NULL
and rely entirely on the key path string to determine the origin of a change notification, but this approach may cause problems for an object whose superclass is also observing the same key path for different reasons.
context可以是任意数据,可以用NULL,完全依靠path来确定通知的来源,但这样会存在一个问题,就是父类也通过这个path来观察
一个很好的选择就是 用static变量指针作为context,这样基本上父类 子类都不会重复的,而完全跳过了字符串的比较过滤,在解析通知上更效率了
An observer does not automatically remove itself when deallocated. The observed object continues to send notifications, oblivious to the state of the observer. However, a change notification, like any other message, sent to a released object, triggers a memory access exception. You therefore ensure that observers remove themselves before disappearing from memory.
observer不会自动remove掉,被观察到对象在observer内存被回收之后,会继续向已释放的内存地址发送通知,这是一件悲催的事情。因此你要确保observers在内存被回收之前执行remove
手动档 自动档
automaticallyNotifiesObserversOf....
关闭自动通知,可以通过 willChange didChange 来手动发送通知
组合观察
progress由 received 与 total 计算而来
可变数组的观察
可变数组的观察
发现,数组元素发生变化,并没有回调观察者监听方法
官方文档开始就预警了
官方文档开始就预警了
为了理解KVO,必须先理解kvc
可变数组发生变化,并没有通知观察者,我们回到KVC文档查看
文档说的很清楚,创建一个可变content,再通过setValue:forKey:
存储回去
这种方法 苹果额外提供了一个技能 对于集合 继续遵从维持kvo
这样的话,那上面的问题就很容易解决了
change 字典里的kind 有 1 2
测试发现 当替换可变数组元素时,kind变为4
原理入门
这是官方文档上的一段描述 大致意思
自动KVO 是通过 一个isa替换的技能实现的
当为一个对象注册observer时,它的isa就改变了,指向了一个中间类
所以你不能用isa来判断一个类,而应该通过 class方法来判断,原因就是你不知道你判断的对象是否被注册了观察者,而导致它的isa被修改,得到错误的结果
KVO原理猜测下 怎么实现
isa修改
-
修改后的isa指向了一个新的类,这个类里肯定做了一些事
记得之前的AFNetworking一文,利用了OC dynamic特性,做了一个 URLSessionTask resume 替换,为了在 task resume时机添加必要通知,然后继续恢复为原来的resume
这里应该也大差不差,在调用设置值 也就是 setXX 方法的时候,做一些通知,然后再恢复正常的setXX 方法调用
isa被修改后,官方文档也说了,调用class方法还能得到正确的类,那就也得利用swizzling
注册observer之后,通过 runtime api获取类,发现是别的类了
但是通过class获取,类却没有变
第一步猜想得证,动态生成了一个中间类 NSKVONotifying_IFLKVOObject,这我们可以自己模拟去实现了
通过runtime api 打印 类及子类的所有类
发现被注册observer的类 此时多了一个子类 NSKVONotifying_IFLKVOObject
而 NSKVONotifying_IFLKVOObject 打印类及子类 ,只有自己而已
从打印结果看 发现方法没什么特别的,主要是setXX方法,class方法 dealloc
这个class方法 肯定是为了 通过OC class获取到原本的类,与前面的猜想很接近,swizzle
移除观察者之后,类恢复添加观察者之前的状态
但是如果remove observer 是通过 context remove掉的话,并没有完全删除,类状态还无法恢复,所以 remove的时候 直接通过keyPath删除就好了
之前的测试例子里,有一个细节我没提,就是 成员变量并不会通知观察者,也就是并不观察成员变量
监听 被观察对象的name属性,通过堆栈信息 我们看到经历了些什么操作
_NSSetObjectValueAndNotify
-[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:]
-[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:maybeNewValuesDict:usingBlock:]
-[IFLKVOObject setName:](self=0x000060000380cf40, _cmd="setName:", name=@"(null)+1")
自定义KVO实现
具体细节就不在文章里展开了,可以具体上 github 查看
不过需要注意两点
observer循环引用问题
observer可否自动销毁
KVO实现代码 github
iOS架构设计遗留了MVVM,有了KVC KVO的原理知识铺垫,那么接下来就可以谈论MVVM了