原创Blog,转载请注明出处
blog.csdn.net/hello_hwc
前言:KVC和KVO是帮助我们驾驭objective C动态特性工具。KVO是建立在KVC基础上的,所以不了解KVC的同学可以参见我的这篇博客。这里我不会再重复讲解KVC。
http://blog.csdn.net/hello_hwc/article/details/43769765
本文的内容
KVO的定义
KVO的典型使用场景。
手动KVO
几点KVO要说的地方
一 KVO的定义
KVO提供了一种key-value-observing的机制,也就是说可以通过监听key,来获得value的变化。用来在对象之间监听状态变化。使用KVO的类要遵循 协议,事实上,任何继承自NSObject的类,都遵循了这个协议。而Object C中,几乎所有的类都源自NSObject
使用KVO通常分为三步
1.1 订阅想要监听的keypath
用函数
- (void)addObserver:(NSObject *)anObserver
forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
context:(void *)context
注册通知
- observer:观察者,也就是KVO通知的订阅者。订阅着必须实现
observeValueForKeyPath:ofObject:change:context:方法
- keyPath:描述将要观察的属性,相对于被观察者。
- options:KVO的一些属性配置;有四个选项。
- context: 上下文,这个会传递到订阅着的函数中,用来区分消息,所以应当是不同的。
options所包括的内容
- NSKeyValueObservingOptionNew:change字典包括改变后的值
- NSKeyValueObservingOptionOld:change字典包括改变前的值
- NSKeyValueObservingOptionInitial:注册后立刻触发KVO通知
- NSKeyValueObservingOptionPrior:值改变前是否也要通知(这个key决定了是否在改变前改变后通知两次)
1.2 响应状态变化
每当监听的keyPath发生变化了,就会在这个函数中回调。
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
- keyPath:被监听的keyPath , 用来区分不同的KVO监听。
- object: 被观察修改后的对象(可以通过object获得修改后的值)
- change:保存信息改变的字典(可能有旧的值,新的值等)
- context:上下文,用来区分不同的KVO监听。
1.3 在适当的时候,取消订阅
通常使用两个函数
- (void)removeObserver:(NSObject *)anObserver
forKeyPath:(NSString *)keyPath
- (void)removeObserver:(NSObject *)observer
forKeyPath:(NSString *)keyPath
context:(void *)context
一般在remove的时候会这么写,因为remove的时候,无法判读啊remove是否成功了
@try {
[object removeObserver:self forKeyPath:keyPath];
}
@catch (NSException *exception) {
NSLog(@"%@",exception);
}
二 KVO的典型使用场景 - model 与 view的同步
这里,你将看到一个完整的KVO的例子。和上篇KVC一样,我写了个类似的demo。点击random会随机改变User的age,然后UI上要进行同步显示出新的和旧的age。
实现过程如下:
定义了一个User类来作为Model
@interface User : NSObject
@property (strong,nonatomic) NSString * name;
@property (nonatomic) NSUInteger age;
@end
定义两个静态的变量,一个作为keyPath,一个作为context
static NSString * observename = @"age";
static void * privateContext = 0;
然后在viewWillAppear中注册(订阅)KVO,在viewWillDisappear中删除KVO
-(void)viewWillAppear:(BOOL)animated{
[self.user addObserver:self
forKeyPath:observename
options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:privateContext];
}
-(void)viewWillDisappear:(BOOL)animated{
@try {
[self.user removeObserver:self forKeyPath:observename];
}
@catch (NSException *exception) {
NSLog(@"%@",exception);
}
}
当点击random的时候,age会改变
- (IBAction)random:(id)sender {
self.user.age = arc4random()%100 +1;
}
然后,在上面提到的函数中进行model和view的同步
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
if (context == privateContext) {
if ([keyPath isEqualToString:observename]) {
NSNumber * old = [change objectForKey:NSKeyValueChangeOldKey];
NSNumber * new = [change objectForKey:NSKeyValueChangeNewKey];
self.lastvalue.text = [NSString stringWithFormat:@"%@",old];
self.newvalue.text = [NSString stringWithFormat:@"%@",new];
}
}
}
三 手动KVO
KVO的实现,是对注册的keyPath中自动实现了两个函数,在Setter中,自动调用。
- (void)willChangeValueForKey:(NSString *)key
- (void)didChangeValueForKey:(NSString *)key
可能有时候,我们要实现手动的KVO
这时候需要关闭自动生成KVO通知,然后手动的调用,手动通知的好处就是,可以灵活加上自己想要的判断条件。例如
+(BOOL)automaticallyNotifiesObserversOfAge{
return NO;
}
-(void)setAge:(NSUInteger)age{
if (age < 22) {
return;
}
[self willChangeValueForKey:@"age"];
_age = age;
[self didChangeValueForKey:@"age"];
}
四 KVO要提到的几点
KVO和Context
由于Context通常用来区分不同的KVO,所以context的唯一性很重要。通常,我的使用方式是通过在当前.m文件里用静态变量定义。
static void * privateContext = 0;
KVO与线程
KVO的响应和KVO观察的值变化是在一个线程上的,所以,大多数时候,不要把KVO与多线程混合起来。除非能够保证所有的观察者都能线程安全的处理KVO
监听变化的值
改变前和改变后分别为
id oldValue = change[NSKeyValueChangeOldKey];
id newValue = change[NSKeyValueChangeNewKey];