KVO原理

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的一些气息

相信没有不会使用的

image.png

我们可以通过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,这样基本上父类 子类都不会重复的,而完全跳过了字符串的比较过滤,在解析通知上更效率了

image.png
  • 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....

image.png
image.png

关闭自动通知,可以通过 willChange didChange 来手动发送通知

组合观察

image.png

progress由 received 与 total 计算而来

image.png

可变数组的观察

可变数组的观察

发现,数组元素发生变化,并没有回调观察者监听方法

image.png

官方文档开始就预警了

官方文档开始就预警了

为了理解KVO,必须先理解kvc

可变数组发生变化,并没有通知观察者,我们回到KVC文档查看

image.png

文档说的很清楚,创建一个可变content,再通过setValue:forKey: 存储回去

这种方法 苹果额外提供了一个技能 对于集合 继续遵从维持kvo

这样的话,那上面的问题就很容易解决了

image.png
image.png

change 字典里的kind 有 1 2

测试发现 当替换可变数组元素时,kind变为4

image.png

原理入门

image.png

这是官方文档上的一段描述 大致意思

自动KVO 是通过 一个isa替换的技能实现的

当为一个对象注册observer时,它的isa就改变了,指向了一个中间类

所以你不能用isa来判断一个类,而应该通过 class方法来判断,原因就是你不知道你判断的对象是否被注册了观察者,而导致它的isa被修改,得到错误的结果

KVO原理猜测下 怎么实现

  • isa修改

  • 修改后的isa指向了一个新的类,这个类里肯定做了一些事

    记得之前的AFNetworking一文,利用了OC dynamic特性,做了一个 URLSessionTask resume 替换,为了在 task resume时机添加必要通知,然后继续恢复为原来的resume

    这里应该也大差不差,在调用设置值 也就是 setXX 方法的时候,做一些通知,然后再恢复正常的setXX 方法调用

  • isa被修改后,官方文档也说了,调用class方法还能得到正确的类,那就也得利用swizzling

image.png

注册observer之后,通过 runtime api获取类,发现是别的类了

image.png

但是通过class获取,类却没有变

第一步猜想得证,动态生成了一个中间类 NSKVONotifying_IFLKVOObject,这我们可以自己模拟去实现了

image.png

通过runtime api 打印 类及子类的所有类

发现被注册observer的类 此时多了一个子类 NSKVONotifying_IFLKVOObject

而 NSKVONotifying_IFLKVOObject 打印类及子类 ,只有自己而已

image.png

从打印结果看 发现方法没什么特别的,主要是setXX方法,class方法 dealloc

这个class方法 肯定是为了 通过OC class获取到原本的类,与前面的猜想很接近,swizzle

image.png

移除观察者之后,类恢复添加观察者之前的状态

但是如果remove observer 是通过 context remove掉的话,并没有完全删除,类状态还无法恢复,所以 remove的时候 直接通过keyPath删除就好了

之前的测试例子里,有一个细节我没提,就是 成员变量并不会通知观察者,也就是并不观察成员变量

image.png

监听 被观察对象的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实现

image.png

具体细节就不在文章里展开了,可以具体上 github 查看

不过需要注意两点

  • observer循环引用问题

  • observer可否自动销毁

KVO实现代码 github

iOS架构设计遗留了MVVM,有了KVC KVO的原理知识铺垫,那么接下来就可以谈论MVVM了

你可能感兴趣的:(KVO原理)