IOS SDK详解之KVO

原创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];

你可能感兴趣的:(ios,Foundation,KVO)