以前有对KVO的基本使用做过总结,最近对于KVO底层这一块又重新分析了一下,所以在这里重新记录一下使用,并叙述一下KVO底层机制。
kvo,顾名思义,是一种键值监听,也是一种观察者模式,简单的来说,在某个对象注册监听者后/在被监听的对象发生改变时/对象会发送一个通知给监听者/以便监听者执行回调
就是每次指定的被观察的对象的属性被修改后 /KVO就会自动通知响应的观察者
系统实现KVO的步骤
- 当类A的对象第一次进行监听器注册后,系统会在运行期动态创建类A的派生类。我们称为B(NSKVONotifying_A),该类继承原有类。
- 在这个过程中, 被观察对象的isa指针从指向原来A类的, 被KVO机制修改为指向B(NSKVONotifying_A)类, 来实现当前类属性值改变的监听。
- 接着,会在派生类中修改监听属性的setter方法,执行willChangeValueForKey:和didChangeValueForKey:方法,并通知所有监听的对象,监听属性被修改了,继而 observeValueForKey:ofObject:change:context: 也会被调用。
因此,对于使用KVO监听的类来说,isa指针的指向并不一定指向对象的实际类。你不应该依赖isa指针取决定类的成员关系,而应该使用class方法去正确的获取对象的实际类。
基本使用
- 注册
1 | - addObserver: forKeyPath: options: context |
- 实现
1 | - observeValueForKeyPath: ofObject: change: context: |
- 移除
1 | - removeObserver: forKeyPath: |
优势
对比其他的回调方式,KVO 机制的运用的实现,更多的由系统支持,相比 notification、delegate 等更简洁些,并且能够提供观察属性的最新值以及原始值;但是相应的在创建子类、重写方法等等方面的内存消耗是很巨大的。所以对于两个类之间的通信,我们可以根据实际开发的环境采用不同的方法,使得开发的项目更加简洁实用。
另外需要注意的是,由于这种继承方式的注入是在运行时而不是编译时实现的,如果给定的实例没有观察者,那么 KVO 不会有任何开销,因为此时根本就没有 KVO 代码存在。但是即使没有观察者,委托和 NSNotification 还是得工作,这也是KVO此处零开销观察的优势。
手动触发KVO
应用场景
首先KVO默认是自动触发的,在某些情况下,我们需要手动控制它的触发,比如某个属性name,我不想他触发kvo,或者属性name值为“123”的时候,我不想让它触发kvo。
解决方案
这种情况下,我们需要从KVO的底层实现原理入手。KVO在第一次注册属性的时候,会默认调用+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key方法,此方法系统默认返回YES的,这里的key就是属性,如果返回NO的话,接下来改变属性的时候,是不会触发willChangeValueForKey和didChangeValueForKey,从而就不会走KVO实现方法observeValueForKeyPath(事实上是当系统同时调用了willChangeValueForKey和didChangeValueForKey两个方法后,才会触发observeValueForKeyPath方法)。
举个栗子如下:
1 | @interface Bird : NSObject |
上面的栗子就是在属性为name的时候,我们将系统默认的willChangeValueForKey 和 didChangeValueForKey 屏蔽了,也就是在automaticallyNotifiesObserversForKey中返回NO,然后我们在name的set方法里,进行了手动调用willChangeValueForKey和didChangeValueForKey两个方法,发现仍然是可以触发KVO的observeValueForKeyPath方法的(当然了,在name的set方法里,我们还可以根据需求做一些别的判断操作,比如这里name为123的时候,我不希望他触发kvo,所以就直接给他return掉了,不让他走下面的那两个手动调用的方法)。
*也就是说,手动触发kvo的前提是+ (BOOL)automaticallyNotifiesObserversForKey:(NSString )key方法返回NO,从而屏蔽掉系统自动触发willChangeValueForKey和didChangeValueForKey,然后我们自己在set方法里根据自己的需要做操作,最后重写一下willChangeValueForKey和didChangeValueForKey两个方法,就会触发observeValueForKeyPath。
2019-5-25更新
在swift里,还是要靠拢oc代码的,比如swift纯类(结构体)需要继承NSObject类,被观察的属性需要用@objc dynamic
修饰等,具体代码如下:
1 | import UIKit |