基础使用
使用KVO需要三个步骤:
- 在观察者中,调用被观察者的
addObserver:forKeyPath:options:context:
进行注册 - 在观察者中实现
observeValueForKeyPath:ofObject:change:context:
方法 - 在观察者中使用
removeObserver:forKeyPath:
移除KVO,一般可以在dealloc
方法中移除,否则会导致Crash
1 注册观察者
比方说ViewController
是观察者,person
是其属性。ViewController
需要监听person
中的age属性
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person addObserver:self forKeyPath:@"age" options:options context:nil];
[self.person addObserver:self forKeyPath:@"numArray" options:options context:nil];
options
是一个NS_OPTIONS
枚举,可以决定以下内容
- 通知发出的时机
-
observeValueForKeyPath:ofObject:change:context:
方法中,字典参数change字典中包含哪些值
参数options
有四个值
-
NSKeyValueObservingOptionNew
,change字典中应该包含改变后的新值 -
NSKeyValueObservingOptionOld
,change字典中应该包含改变前的旧值 -
NSKeyValueObservingOptionInitial
,addObserver:forKeyPath:options:context:
消息被发出去后,甚至不用等待这个消息返回,监听者对象会马上收到一个通知。这种通知只会发送一次,你可以利用这种“一次性“的通知来确定要监听属性的初始值。当同时制定这3个选项时,这种通知的change字典中只会包含新值,而不会包含旧值。虽然这时候的新值实际上是改变前的'旧值',但是这个值对于监听者来说是新的。 -
NSKeyValueObservingOptionPrior
:当指定了这个选项时,在被监听的属性被改变前,监听者对象就会收到一个通知(一般的通知发出时机都是在属性改变后,虽然change字典中包含了新值和旧值,但是通知还是在属性改变后才发出),这个通知会包含一个NSKeyValueChangeNotificationIsPriorKeykey
,其对应的值为一个NSNumber类型的YES。当同时指定该值、new和old的话,change字典会包含旧值而不会包含新值。
2 接收通知
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<nskeyvaluechangekey, id> *)change context:(nullable void *)context;</nskeyvaluechangekey, id>
关于change参数,它是一个字典,有五个常量作为它的键:
-
NSKeyValueChangeKindKey
:指明了变更的类型,值为NSKeyValueChange
枚举中的某一个,类型为NSNumber。- 一般情况下返回的都是第一个NSKeyValueChangeSetting
- 如果监听的属性是一个集合对象的话,当这个集合中的元素被插入,删除,替换时,就会分别返回
NSKeyValueChangeInsertion
,NSKeyValueChangeRemoval
和NSKeyValueChangeReplacement
。
enum {
NSKeyValueChangeSetting = 1,
NSKeyValueChangeInsertion = 2,
NSKeyValueChangeRemoval = 3,
NSKeyValueChangeReplacement = 4
};
typedef NSUInteger NSKeyValueChange;
-
NSKeyValueChangeNewKey
:被监听属性改变后新值的key。当监听属性为一个集合对象,且NSKeyValueChangeKindKey
不为NSKeyValueChangeSetting
时,该值返回的是一个数组,包含插入,替换后的新值(删除操作不会返回新值)。 -
NSKeyValueChangeOldKey
:被监听属性改变前旧值的key。当监听属性为一个集合对象,且NSKeyValueChangeKindKey
不为NSKeyValueChangeSetting
时,该值返回的是一个数组,包含删除,替换前的旧值(插入操作不会返回旧值) -
NSKeyValueChangeIndexesKey
:如果NSKeyValueChangeKindKey
的值为NSKeyValueChangeInsertion
,NSKeyValueChangeRemoval
, 或者NSKeyValueChangeReplacement
,这个键的值是一个NSIndexSet
对象,包含了增加,移除或者替换对象的index
。 -
NSKeyValueChangeNotificationIsPriorKey
:如果注册监听者是options
中指明了NSKeyValueObservingOptionPrior
,change字典中就会带有这个key
,值为NSNumber
类型的YES
.
3. 移除监听
当一个监听者完成了它的监听任务之后,就需要注销(移除)监听者,调用以下2个方法来移除监听。通常会在-dealloc方法或者observeValueForKeyPath:ofObject:change:context:
方法中移除。
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context
或者
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
有几点需要注意的:
当你向一个不是监听者的对象发送remove
消息的时候(也可能是,你发送remove
消息时,接受消息的对象已经被remove
了一次,或者在注册为监听者前就调用了remove
),xcode
会抛出一个NSRangeException
异常,所以,保险的做法是,把remove
操作放在try/catch
中。
一个监听者在其被销毁时,并不会自己注销监听,而给一个已经销毁的监听者发送通知,会造成野指针错误。所以至少保证,在监听者被释放前,将其监听注销。保证有一个add
方法,就有一个remove
方法。
4 触发KVO
触发KVO的方式包括:
- 点语法
- set方法
- kvc
- 如果监听的是集合属性,以数组为例,得使用被观察者
mutableArrayValueForKey
,进行操作
[[self.person mutableArrayValueForKey:@"numArray"] addObject:@(randNum)];
KVO原理
当某个对象第一次被观察时,系统就会在运行期动态地创建该类的一个子类类对象(类名就是在该类的前面加上NSKVONotifying_ 前缀),被观察对象的isa指针就指向这个类对象。这个子类类对象,会有如下的变化:
- 被观察属性的
set
方法被修改 - 修改
class
实例方法,让其返回原始的类对象 -
dealloc
和_isKVOA
KVO之后类的关系.png
参考
https://www.jianshu.com/p/badf5cac0130
Demo地址:https://github.com/xiaoLong1010/DeepObjectiveC/tree/master/03-KVO
网友评论