概述
KVO全称为Key Value Observing,键值监听机制,由NSKeyValueObserving协议提供支持,NSObject类继承了该协议,所以NSObject的子类都可使用该方法。
KVO全称KeyValueObserving,是苹果提供的一套事件通知机制。允许对象监听另一个对象特定属性的改变,并在改变时接收到事件。由于KVO的实现机制,所以对属性才会发生作用,一般继承自NSObject的对象都默认支持KVO。
KVO和NSNotificationCenter都是iOS中观察者模式的一种实现。区别在于,相对于被观察者和观察者之间的关系,KVO是一对一的,而一对多的。KVO对被监听对象无侵入性,不需要修改其内部代码即可实现监听。
KVO可以监听单个属性的变化,也可以监听集合对象的变化。通过KVC的mutableArrayValueForKey:等方法获得代理对象,当代理对象的内部对象发生改变时,会回调KVO监听的方法。集合对象包含NSArray和NSSet。
基础使用
使用KVO分为三个步骤:
通过addObserver:forKeyPath:options:context:方法注册观察者,观察者可以接收keyPath属性的变化事件。
在观察者中实现observeValueForKeyPath:ofObject:change:context:方法,当keyPath属性发生改变后,KVO会回调这个方法来通知观察者。
当观察者不需要监听时,可以调用removeObserver:forKeyPath:方法将KVO移除。需要注意的是,调用removeObserver需要在观察者消失之前,否则会导致Crash。
注册方法
在注册观察者时,可以传入options参数,参数是一个枚举类型。如果传入NSKeyValueObservingOptionNew和NSKeyValueObservingOptionOld表示接收新值和旧值,默认为只接收新值。如果想在注册观察者后,立即接收一次回调,则可以加入NSKeyValueObservingOptionInitial枚举。
还可以通过方法context传入任意类型的对象,在接收消息回调的代码中可以接收到这个对象,是KVO中的一种传值方式。
在调用addObserver方法后,KVO并不会对观察者进行强引用,所以需要注意观察者的生命周期,否则会导致观察者被释放带来的Crash。
监听方法
观察者需要实现observeValueForKeyPath:ofObject:change:context:方法,当KVO事件到来时会调用这个方法,如果没有实现会导致Crash。change字典中存放KVO属性相关的值,根据options时传入的枚举来返回。枚举会对应相应key来从字典中取出值,例如有NSKeyValueChangeOldKey字段,存储改变之前的旧值。
change中还有NSKeyValueChangeKindKey字段,和NSKeyValueChangeOldKey是平级的关系,来提供本次更改的信息,对应NSKeyValueChange枚举类型的value。例如被观察属性发生改变时,字段为NSKeyValueChangeSetting。
如果被观察对象是集合对象,在NSKeyValueChangeKindKey字段中会包含NSKeyValueChangeInsertion、NSKeyValueChangeRemoval、NSKeyValueChangeReplacement的信息,表示集合对象的操作方式。
KVO实现步骤
1.注册观察者(为被观察这指定观察者以及被观察者属性)
/*
options: 有4个值,分别是:
NSKeyValueObservingOptionOld 把更改之前的值提供给处理方法
NSKeyValueObservingOptionNew 把更改之后的值提供给处理方法
NSKeyValueObservingOptionInitial 把初始化的值提供给处理方法,一旦注册,立马就会调用一次。通常它会带有新值,而不会带有旧值。
NSKeyValueObservingOptionPrior 分2次调用。在值改变之前和值改变之后。
*/
//注册一个监听器用于监听指定的key路径[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
2.实现回调方法
//当key路径对应的属性值发生改变时,监听器就会回调自身的监听方法,如下- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)contex
}
3.触发回调方法
注意:路径keyPath请参考上一篇KVO中的说明
4.移除观察者
//删除指定的key路径监听器
[self.person removeObserver:self forKeyPath:@"name"];
//删除指定的key路径监听器,只是多了context参数
[self.person removeObserver:self forKeyPath:@"name" context:nil];
KVO的主要应用场景
应用场景:当数据模型的数据发生改变时,视图组件能动态的更新,及时显示数据模型更新后的数据。
比如:监听scrollView的contentOffset属性,来完成用户滚动时动态改变某些控件的属性实现效果,包括渐变导航栏、下拉刷新控件等效果。
KVO的实现原理简要说明
KVO 是基于 runtime 机制实现的 当某个类的属性对象第一次被观察时,系统就会在运行期动态地创建该类的一 个派生类,在这个派生类中重写基类中任何被观察属性的 setter 方法。派生类在 被重写的 setter 方法内实现真正的通知机制。
如果原类为 Person,那么生成的派生类名为 NSKVONotifying_Person 每个类对象中都有一个 isa 指针指向当前类,当一个类对象的第一次被观察,那么 系统会偷偷将 isa 指针指向动态生成的派生类,从而在给被监控属性赋值时执行的是派生类的 setter 方法。
键值观察通知依赖于 NSObject 的两个方法willChangeValueForKey:,didChangevlueForKey:
在一个被观察属性发生改变之前, willChangeValueForKey: 一定会被调用,这就 会记录旧的值。而当改变发生后,didChangeValueForKey: 会被调用,
继而observeValueForKey:ofObject:change:context: 也会被调用。
补充:KVO 的这套实现机制中苹果还偷偷重写了 class 方法,让我们误认为还是使用的当前类,从而达到隐藏生成的派生类。
注意点
KVO的addObserver和removeObserver需要是成对的,如果重复remove则会导致NSRangeException类型的Crash,如果忘记remove则会在观察者释放后再次接收到KVO回调时Crash。
网友评论