KVO(Key-Value-Observing)键值观察
监听property的变化 通知某些对象(观察者)关于其他对象属性值发生变化的一种机制.
优缺点
优点
性能好[开销相对于NSNotification和委托更小(只用存取方法来修改实例变量,不需要额外成本)];
容易实现视图组件和数据模型的分离,模型类的简洁;
缺点
回调方法中传递的代表变化的字典,用起来繁琐;bug难解决(会制造出人意料的代码执行路径,有一些代码运行但没有任何可见的代码说明行为发生的原因);
DEMO
// from AFNetworking--AFURLSessionManager
// 添加监听对象(注册指定key路径的监听器)
[task addObserver:self
forKeyPath:NSStringFromSelector(@selector(countOfBytesReceived))
options:NSKeyValueObservingOptionNew
context:NULL];
// 移除监听对象(删除指定keyPath的监听器)
[task removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesReceived))];
// 回调监听
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
if ([object isKindOfClass:[NSURLSessionTask class]] || [object isKindOfClass:[NSURLSessionDownloadTask class]]) {
if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesReceived))]) {
self.downloadProgress.completedUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
}
// ...
}
最佳实践
尽量保守,简单的使用KVO; 在存在复杂相互依赖关系或者复杂的类继承层次的地方避免使用.
– 9th,August,2016
注册键值依赖
// from AFNetworking
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
if ([key isEqualToString:@"reachable"] || [key isEqualToString:@"reachableViaWWAN"] || [key isEqualToString:@"reachableViaWiFi"]) {
return [NSSet setWithObject:@"networkReachabilityStatus"];
}
return [super keyPathsForValuesAffectingValueForKey:key];
}
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:@"fullName"]) {
NSArray *affectingKeys = @[@"lastName", @"firstName"];
keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
}
return keyPaths;
}
Tips
– 5th,April,2017
添加移除位置
UIViewController中在viewWillAppear中添加键值观察,在viewWillDisappear中移除键值观察。或者init与dealloc中添加和移除键值观察。
UIView在awakeFromNib中添加键值观察,在removeFromSuperview中移除键值观察。
ps: 如果在不恰当的地方(比如UIView中在willRemoveSubview中)移除可能会导致崩溃“Cannot remove an observer because it is not registered as an observer.”,也不可在ViewDidDisappear中移除,比如在B页面的ViewDidDisappear中移除通知,页面跳转逻辑为A->B->C,B跳到C页面调用一次,B返回A页面调用一次,就会出现崩溃问题。
无法接收到观察通知:
一般就3个理由,1) 添加键值观察时对象为空;2) 通知已经发送,然后才添加的键值观察; 3)添加键值观察的对象被销毁,比如为weak或其他情况导致无法接收。
参考资料
log
5th,April,2017 – add Tips2
25th,October,2015