KVO,全称为Key-Value Observing,是iOS中的一种设计模式,用于检测对象的某些属性的实时变化情况并作出响应
首先,假设我们的目标是在一个UITableViewController内对tableview的contentOffset进行实时监测,很容易地使用KVO来实现为。
在初始化方法中加入:
[_tableView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil];
在dealloc中移除KVO监听:
[_tableView removeObserver:self forKeyPath:@"contentOffset" context:nil];
添加默认的响应回调方法:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
[self doSomethingWhenContentOffsetChanges];
}
注意事项
1.当你在controller中添加多个KVO时,所有的回调都是走同上述函数,那就必须对触发回调函数的来源进行判断。
判断如下:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (object == _tableView && [keyPath isEqualToString:@"contentOffset"]) {
[self doSomethingWhenContentOffsetChanges];
}
}
2.假设当前类(在例子中为UITableViewController)还有父类,并且父类也有自己绑定了一些其他KVO呢?我们看到,上述回调函数体中只有一个判断,如果这个if不成立,这次KVO事件的触发就会到此中断了。但事实上,若当前类无法捕捉到这个KVO,那很有可能是在他的superClass,或者super-superClass…中,上述处理砍断了这个链。
合理的处理方式应该是这样的:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (object == _tableView && [keyPath isEqualToString:@"contentOffset"]) {
[self doSomethingWhenContentOffsetChanges];
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
3.潜在的问题有可能出现在dealloc中对KVO的注销上。KVO的一种缺陷(其实不能称为缺陷,应该称为特性)是,当对同一个keypath进行两次removeObserver时会导致程序crash,这种情况常常出现在父类有一个kvo,父类在dealloc中remove了一次,子类又remove了一次的情况下。
* 我们可以分别在父类以及本类中定义各自的context字符串,比如在本类中定义context为@"ThisIsMyKVOContextNotSuper";然后在dealloc中remove observer时指定移除的自身添加的observer。
这样iOS就能知道移除的是自己的kvo,而不是父类中的kvo,避免二次remove造成crash。