TIP_2017_3_16

Tip

在继承关系的类中使用KVO引发的问题

如下面的代码,类AA继承自A,若A中也使用了KVO,实现了observeValueForKeyPath:ofObject:change:context:方法,就需要在类AA中通过调用[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];方法执行A中的实现的observeValueForKeyPath:ofObject:change:context:方法,但若类A中没有实现则会因为调用[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];引发崩溃(虽然NSObject类的NSKeyValueObserving的扩展中定义了observeValueForKeyPath:ofObject:change:context:方法,但是却没有实现这个方法)。因此,在使用KVO时要注意在继承关系中使用KVO。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@interface AA: A
@end
@implementation AA
- (void)dealloc
{
[self.xx removeObserver:self forKeyPath:@"xxxx"];
}
- (void)addObserver {
[self.xx addObserver:self forKeyPath:@"xxxx" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew context:NULL];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
if ([super respondsToSelector:@selector(observeValueForKeyPath:ofObject:change:context:)]) {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
if ([object isEqual:self.xx] && [keyPath isEqualToString:@"xxxx"]) {
// TODO
}
}
@endif

解决方法:

  1. 添加一个NSObject的扩展,实现observeValueForKeyPath:ofObject:change:context:空方法。这样通过[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];的方式调用就不会出问题了。
  2. 添加一个单独的KVO的Observer类。(1)在Observer类中实现observeValueForKeyPath:ofObject:change:context:方法,(2)通过Block的方式将处理KVO通知的代码在具体业务逻辑中实现,然后传给Observer类,(3)Observer类收到KVO通知调用object对应的block(s)就可以了。这样的好处是只用在Observer类中实现observeValueForKeyPath:ofObject:change:context:方法,具体的业务中只负责实现处理KVO通知的代码,实现了代码分离;同一个object可以有多个block。

    另:可以把Observer实现为一个单例(FBKVOController),也可以以runtime的方式为NSObject增加一个字典变量,以KVO的keyPath为key,Observer对象为value,实现一个简单实用的KVO自定义方案(YYKit)。

KVO注意事项:

由于KVO天生的娇惯特质(1. 必须先addObserver,再removeObserver;2. 在receiver释放后必须同时remove掉其对应的所有Observers),在多线程(异步)的情况下可能不能保证add与remove的顺序,以及开发者忘记在写removeObserver方法等原因造成App崩溃。因此在编写KVO代码时需要十分小心。

可参考FBKVOController中对KVO的处理。