前言:已经有一些很成熟的文章介绍kvo的原理iOS底层原理总结,英文好的同学可以直接看这篇。这里只是笔者的一些遐想,如果我设计kvo,我要怎么做。
把大象装进冰箱需要几步:
需要实现的功能:实时获取到属性变更,并通知观察者
第一步:需要知道这个属性变化了
第二步:把属性的变化送到观察者中去
第一步:
方法1 - 手动重写set方法:
属性变化被通知,让我实现,我肯定想的是在set方法中做文章。重写set方法,调用外部的类去传达这个变化。
但是这个需要重写set方法才能支持kvo。这样使用kvo的业务层会很头疼,每个能被观察的属性都需要重写set方法。
方法2 - Method Swizzling:
使用runtime功能,调换set方法,实现自动重写。这个需要在进程加载的时候使用runtime统一去做。
这种弊端太明显。业务层也有可能进行Method Swizzling,如果业务层swizzle后不调用原方法,kvo就失效了。而且一旦发生问题,很难调试。
方法3 - 编译时处理:
像ARC一样,在编译的时候手动给set方法加上需要的处理
这样实现的话,所有实例的属性变化的时候都会往外面调用方法,告诉外面我的值变了,不管该属性有没有被观察。这就需要中间加一层,查找该属性有没有被观察。所以每次类属性赋值,都要查询一遍观察者表。
系统的解决方案:
系统的方案是在运行时解决这件事。在每次属性被观察的时候,生成一个子类模版,子类模版里覆写set方法,并且把被观察的对象isa指针指向自动生成的子类。系统的方案优于方案三的地方是,减少了观察者表的查询。只有属性被观察了,属性的改变才会去查询观察者表。
第二步:
这里没查到相关资料,不过这步也比较简单了。以下是笔者猜想的实现逻辑:
需要一个观察者表,在add observer的时候,把观察者添加到以被观察者地址为key的一个表里。
{
"被观察者地址 + keyPath": ["观察者地址", "观察者地址"]
}
这样每次被观察者的属性发生变化的时候,就去表里寻找自己相关属性的观察者,然后调用观察者的observeValueForKeyPath方法。
观察者是一个数组,这样可以允许多个观察者同时观察一个对象。这个数组是不去重的,实际使用kvo的时候会发现,如果对同一个属性的add observer两次,observeValueForKeyPath就会执行两次。
"被观察者地址 + keyPath" 是为了区分观察者观察的是哪一个属性,防止没有观察的属性发生变化也调用观察者的observeValueForKeyPath。