KVO系列之基础篇

KVO

Key-Value observing(KVC),键值观察,它提供一种机制,当被观察的对象的属性被修改后,KVO会自动通知相对应的观察者。接下来我会演示一下KVO的例子。

观察Model里的属性变化

废话不多说,直接上代码。现有Model: Person

@interface Person : NSObject

@property (nonatomic, copy) NSString *name;

@property (nonatomic, strong) NSNumber *age;

@end

我们来通过KVO动态监听Person中name, age的变化。这里是标准的操作流程:

第一步:注册,指定被观察者对象

[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];

[self.person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];

第二步:实现回调方法

- (void)observeValueForKeyPath:(NSString *)keyPath  ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if ([keyPath isEqualToString:@"name"]) {
        self.titleLabel.text = self.person.name;
    }

}

第三步:移除观察者,如果没有移除观察者,会引发异常,这也是KVO不方便的一点

[self.person removeObserver:self forKeyPath:@"name"];

[self.person removeObserver:self forKeyPath:@"age"];

如上:实现这三部,一个简单的KVO,我们就搞定了。是不是 so easy!

经典案例

下拉刷新,通过监听 frame 来改变动画效果

创建 TableViewController,自定义UIRefreshControl,在这里边监听 frame,来改变动画效果

[self addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionNew context:nil];

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {

if (self.frame.origin.y > 0 ) {return;}

if (self.frame.origin.y < -60 ) {
    [UIView animateWithDuration:0.5 animations:^{
        self.refreshView.arrowView.transform = CGAffineTransformRotate(self.refreshView.arrowView.transform, M_PI);
        self.refreshView.titleLabel.text = @"下拉刷新";
    } completion:^(BOOL finished) {
    }];
}else if (self.frame.origin.y >= -60){
[UIView animateWithDuration:0.5 animations:^{
    self.refreshView.arrowView.transform = CGAffineTransformRotate(self.refreshView.arrowView.transform, M_PI);
    self.refreshView.titleLabel.text = @"松手返回";
    } completion:^(BOOL finished) {
    }];
    }
}

关联依赖

是不是说,掌握了第一部分,我们就能是使用KVO了。答案是肯定的,如果各位看官不想在继续了解的话,请按左上角【返回】按钮。哈哈哈,开玩笑啦,接下来给大家介绍一个好玩的例子,关联依赖。

废话不多说,直接上代码,我就是这么痛快

假设有Person类,其中有三个属性: firstName, lastName, fullName,那当我们要动态的观察 fullName时,怎么办呢。我们都知道fullName 是跟 firstName, lastName有关联的,依据上文说到的方法,这时我们要注册三个观察对象,这很low,这时,依赖关联可以登场了。

第一步: 重写 + (NSSet *)keyPathsForValuesAffection<键名>方法

// 设置 fullName 依赖 firstName, lastName

// 注册观察 fullName, 当firstName, lastName变化时,就能收到通知

+ (NSSet *)keyPathsForValuesAffectingFullName {
    NSSet *set = [NSSet setWithObjects:@"firstName",@"lastName", nil];
    return set;
}

第二步:之后操作同上

[self.person addObserver:self forKeyPath:@"fullName" options: NSKeyValueObservingOptionPrior context:nil];

这时,我们在改变 firstName, lastName中任意一个属性值的时候,我们通过注册的 fullName 都可以获取到,是不是很神奇啊!

手动通知 VS 自动通知

有没有感觉KVO很神奇,但实际上发生的事情是:当 name 的实例 -setName: 方法被调用的时候,系统自动在之前与之后帮我们添加了部分代码:

- (void)willChangeValueForKey:(NSString *)key

- (void)didChangeValueForKey: (NSString *)key

他们分别会在 setName: 中的代码之前与之后调用。有些时候,我们需要自己来控制是否发送通知,或者改变某些特殊的数据内容,就可以做如下操作:

第一步:重写 setter 方法,并手动添加通知前后方法

-(void)setName:(NSString *)name {
    [self willChangeValueForKey:@"name"];
    // 这里可以对处理特别操作
    _name = [NSString stringWithFormat:@"%@123", name];
    [self didChangeValueForKey:@"name"];
}

第二步: 实现 automaticallyNotifiesObserversForKey,return NO

// 通过此方法,决定是否发送通知

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
    // 如果是 name 则,手动发送通知
    if ([key isEqualToString:@"name"]) {
        return NO;
    }
    // 其他,则自动发送通知
    return [super automaticallyNotifiesObserversForKey:key];
}

参数详解

addObserver:forKeyPath: options:context:

observer:观察者,一般都是 self(本身)

keyPath: 被观察的属性名称,例如上文中的 @"name", @"age"

options: 观察属性的新值、旧值等的一些配置(枚举值)

NSKeyValueObservingOptionNew = 0x01,

NSKeyValueObservingOptionOld = 0x02,

NSKeyValueObservingOptionInitial = 0x04,

NSKeyValueObservingOptionPrior = 0x08

四个值的含义如下:

NSKeyValueObservingOptionNew:接收方法中使用change参数传入变化后的新值,键为:NSKeyValueChangeNewKey;

NSKeyValueObservingOptionOld:接收方法中使用change参数传入变化前的旧值,键为:NSKeyValueChangeOldKey;

NSKeyValueObservingOptionInitial:注册之后立刻调用接收方法,如果配置了NSKeyValueObservingOptionNew,change参数内容会包含新值,键为:NSKeyValueChangeNewKey;

NSKeyValueObservingOptionPrior:如果加入这个参数,接收方法会在变化前后分别调用一次,共两次,可以分别获取变化前后不通的值

context: 上下文,可以为kvo的回调方法传值

observeValueForKeyPath: ofObject: change:context:

keyPath:属性名称,通过它可以获取到当前观察的是哪个属性

object:被观察的对象

change:变化前后的值都存储在change字典中

context:注册观察者时,context传过来的值

线程

KVO 是线程同步的,发生变化的值与所观察的值在同一个线程上。

我们可以在改变被观察对象的值时,来开启一个线程,在回调方法中查看是否同属于同一个线程。

[self performSelectorInBackground:@selector(changeValue) withObject:nil];

- (void)changeValue {
    self.starThreadLabel.text = [NSString stringWithFormat:@"开启线程:%@", [NSThread currentThread]];
    self.person.name = @"我是子线程"
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    NSLog(@"当前线程为:%@",[NSThread currentThread]);
}

PS: 以上都是一家之言,望各位看官多加实验来验证,非喜勿喷。

你可能感兴趣的:(KVO系列之基础篇)