KVO
KVO 即 Key-Value Observing,翻译成键值观察。它是一种观察者模式的衍生。其基本思想是,对目标对象的某属性添加观察,当该属性发生变化时,通过触发观察者对象实现的KVO接口方法,来自动的通知观察者。
观察者模式是什么 一个目标对象管理所有依赖于它的观察者对象,并在它自身的状态改变时主动通知观察者对象。这个主动通知通常是通过调用各观察者对象所提供的接口方法来实现的。
简单来说KVO可以通过监听key,来获得value的变化,用来在对象之间监听状态变化。
KVO使用
注册与解除注册
如果我们已经有了包含可供键值观察属性的类,那么就可以通过在该类的对象(被观察对象)上调用以下两个方法来注册和解除注册
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
observer: 被观察者
keyPath:键路径参数,描述将要观察的属性,相对于被观察者
options:标识KVO希望变化如何传递给观察者,可以使用|进行多选(有四个选项)
context:上下文内存区,通常为nil
options所包括的内容
NSKeyValueObservingOptionNew:change字典包括改变后的值
NSKeyValueObservingOptionOld:change字典包括改变前的值
NSKeyValueObservingOptionInitial:注册后立刻触发KVO通知
NSKeyValueObservingOptionPrior:值改变前是否也要通知(这个key决定了是否在改变前改变后通知两次)
处理变更通知
每当监听的keyPath发生变化了,就会在这个函数中回调。
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
在这里,change 这个字典保存了变更信息,具体是哪些信息取决于注册时的 NSKeyValueObservingOptions。
手动KVO(禁用KVO)
KVO中,当被观察的属性改变时,KVO被触发。举例如下:
KVO监测Person类实例person的name属性。当name值改变时,方法- setName:被调用。此时下面两个方法会在运行- setName:之前之后被调用。
- (void)willChangeValueForKey:(NSString *)key
- (void)didChangeValueForKey:(NSString *)key
但是有时候我们并不是都需要每次改变属性值的前后都调用这两个方法,那么我们这时候就需要手动KVO,先把这两个方法的自动调用给禁用,然后我们再自己手动去实现KVO的通知。代码如下:
+ (BOOL)automaticallyNotifiesObserversForName{ //禁用KVO的自动通知
return NO;
}
- (void)setName:(NSString *)lComponent{
if (_name == name) {
return;
}
[self willChangeValueForKey:@"name"];
_name == name;
[self didChangeValueForKey:@"name"];}
上述代码的automaticallyNotifiesObserversForName方法是选择是否自动通知,我们只需返回NO,系统就不会自动通知,然后再在setName方法中我们手动调用通知的两个方法,甚至可以加上加上自己想要的判断条件。
针对非自动通知的属性,可以分别在变化之前和之后手动调用如下方法(will在前,did在后)来手动通知观察者:
(will/did)ChangeValueForKey:(will/did)ChangeValueForKey:withSetMutation:usingObjects:
(will/did)Change:valuesAtIndexes:forKey:
键值依赖(注册从属key)
有时候一个属性的值依赖于另一对象中的一个或多个属性,如果这些属性中任一属性的值发生变更,被依赖的属性值也应当为其变更进行标记。因此,object 引入了依赖键。要为一对一关系自动触发通知,应该重写keyPathsForValuesAffectingValueForKey或实现一个合适的方法,该方法遵循它为注册依赖键定义的模式。
例如,fullName取决于firstName和lastName。返回fullName的方法可以写成如下:
- (NSString *)fullName {
return [NSString stringWithFormat:@“%@%@”,firstName,lastName];
}
当firstName或lastName发生改变时,必须通知观察fullName属性的程序,因为它们影响这个属性的值。一个解决方案是重写keyPathsForValuesAffectingValueForKey来指定fullName属性依赖于lastName和firstName。
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:@"fullName"]) {
NSArray *affectingKeys = @[@"lastName", @"firstName"];
keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
}
return keyPaths;
}
通过重写keyPathsForValuesAffecting<Key>也可以达到相同的效果。
+ (NSSet *)keyPathsForValuesAffectingFullName {
return [NSSet setWithObjects:@"lastName", @"firstName", nil];
}
KVO和线程
一个需要注意的地方是,KVO 行为是同步的,并且发生与所观察的值发生变化的同样的线程上。没有队列或者 Run-loop 的处理。手动或者自动调用 -didChange... 会触发 KVO 通知。
所以,当我们试图从其他线程改变属性值的时候我们应当十分小心,除非能确定所有的观察者都用线程安全的方法处理 KVO 通知。通常来说,我们不推荐把 KVO 和多线程混起来。如果我们要用多个队列和线程,我们不应该在它们互相之间用 KVO。
KVO 是同步运行的这个特性非常强大,只要我们在单一线程上面运行(比如主队列 main queue),KVO 会保证下列两种情况的发生:
首先,如果我们调用一个支持 KVO 的 setter 方法,如下所示:
self.exchangeRate = 2.345;
KVO 能保证所有 exchangeRate 的观察者在 setter 方法返回前被通知到。
KVO原理
1.automaticallyNotifiesObserversForKey
为YES
时注册观察属性会生成动态子类NSKVONotifying_XXX
2.动态子类观察的是setter
方法
3.动态子类重写了观察属性的setter
方法、dealloc
、class
、_isKVOA
方法
- setter
方法用于观察键值
- dealloc
方法用于释放时对isa
指向进行操作
- class
方法用于指回动态子类的父类
- _isKVOA
用来标识是否是在观察者状态的一个标志位
4.dealloc
之后isa
指向元类
5.dealloc
之后动态子类不会销毁