细数KVO的弊端:
- 所有实现都在同一个方法里调用
- (void)addObserver: (NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
比如说我需要观察tablview的contentsize属性,我这样来写:
[_tableView addObserver:self forKeyPath:@"contentSize" options:0 context:NULL];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
[self configureView];
}
完成了?看起来很简单啊,so young,为了写这个,我们还需要做些额外的工作 orz
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (object == _tableView && [keyPath isEqualToString:@"contentSize"]) {
[self configureView];
}
}
- KVO is string-ly typed
keypath ‘contentSiz’ 是字符串类型的,这就意味着编译器跟解析器不会告诉你这个属性是什么类型的或者存不存在。它只是一个字符串。我们只能使用NSStringFromSelector(@selector(contentSize))
来让编译器告诉我们这个存不存在。
另外,当我们观察一个view controller时,想要获取它scrollview的contentsize
,这时候keypath
为scrollview.contentOffset
。这种情况下我们能做的事就更少了。 - KVO 必须处理父类实现
我们也许还有一个父类也在监听,并实现了这个接口方法,那我们就该调用super方法
if (object == _tableView && [keyPath isEqualToString:@"contentSize"]) {
[self configureView];
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
如果我们一不小心忘了,那就可能导致父类监听失效。
所以保险起见,我们最好不嫌麻烦地调用super方法 orz。
- KVO 在解除注册的时候可能导致crash
我们一般会在-dealloc
中[_tableView removeObserver:self forKeyPath:NSStringFromSelector(@selector(contentSize)) context:NULL];
但值得注意的是,tableview有可能会dealloc两次,这种情况下因为我们尝试remove同一个observance两次,就可能导致我们的app crash掉。另外,当父类也在监听同一属性的时候,也可能会调用两次导致crash。
这个时候是该用到 context ,我们可能会context 当做 self来用,但在父类里,就不好用了。 (因为self不管在父类还是子类中都会指向同一个对象)所以推荐使用静态指针来存储context。
static void *ClassNameTableViewContentSizeContext = &ClassNameTableViewContentSizeContext;
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context == ClassNameTableViewContentSizeContext) {
[self doThing];
} else if (context == OtherContext) {
[self doOtherThing];
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- KVO太难调试
delegate调试时,很容易就追踪到设置它的对象了,但KVO则可能需要在运行时使用isKindOfClass:
来追踪。
这个就不用多举例子了,用过的人都有体会。