当我们需要关注某些对象的变化时,就会用到观察者模式。
对于观察者模式,iOS中提供了两种方式实现:Notification和KVO。
KVO:Key-Value-Observer 键值观察者。基于KVC实现。
在实际使用中,只需要实现三个方法就能完成对目标的观察:
1. - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
2. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void *)context;
3. - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context;
其中,- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context
消息仅在需要取消KVO观察者的时候调用,也就是说,整个KVO的监听设置,只需要前两个消息就能实现。
实际上,当我们发送 - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context
消息后,iOS系统会在runtime动态的创建一个目标类的派生类,将目标类的isa指针指向新的派生类,并在派生类中重写目标对象的setter和getter。同时重写派生类的 -(Class)class
方法和 - (NSString *)description
方法,来隐藏上述实现。新的派生类会固定以NSKVONotifying_+目标类名的方式命名,以区分于原目标类,同时可以避免重复创建派生类。
在- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context
中, NSKeyValueObservingOptions 是一个可选参数列表,我们可以使用如下几个参数:
- NSKeyValueObservingOptionNew
当对象的值被修改后,返回新值。 - NSKeyValueObservingOptionOld
当对象的值被修改后,返回旧值。 - NSKeyValueObservingOptionInitial
在设置观察者的同时,立刻返回当前被观察对象的值。 - NSKeyValueObservingOptionPrior
当对象的值被修改,返回修改之前的旧值,然后返回修改之后的新值。
当通过KVC方式修改目标对象时,会触发以下两个消息。
- (void)willChangeValueForKey:(NSString *)key;
- (void)didChangeValueForKey:(NSString *)key;
对象的值被修改前,在
- (void)willChangeValueForKey:(NSString *)key
中记录下旧值,
修改后,在
- (void)didChangeValueForKey:(NSString *)key
中记录下新值。
之后,就会自动发出通知,回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void *)context
如果需要手动触发KVO的回调,可以做如下修改。
- 重写监听属性的setter、getter方法, 在setter方法中,在赋值的前后分别调用:willChangeValueForKey和didChangeValueForKey;
- 重写 + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key 方法,对要手动触发的key,
return false
,不允许其自动发出通知; - 实现willChangeValueForKey和didChangeValueForKey方法。
补充:
最近面试有被问到这样的问题:对于成员变量,是否能通过KVO来监听?
其实根据上面所介绍的,KVO是基于KVC来查找监听对象的,成员变量和属性在设置监听时并没有实质上的区别,只需要在修改成员变量的值时,不要去直接修改,而是遵循KVC方式( setValue:(nullable id)value forKeyPath:(NSString *)keyPath
),就可以同样对成员变量设置监听了。抑或通过重写成员变量的settter和getter方法,即可像使用属性的KVO设置一样,来使用成员变量的KVO设置。