KVO 解密一:KVO简单使用

本文深入解析苹果的KVO(KeyValueObserving)机制,一种用于对象间属性变化通知的重要模式。KVO允许对象监听另一对象的属性变化,实现一对一的观察者模式。文章详解KVO的基本使用,包括如何注册、实现监听方法及移除监听,同时提供代码示例。此外,还介绍了如何优化KVO,避免不必要的资源消耗,如通过手动控制通知时机来减少无效通知。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

KVO 解密一:KVO简单使用

KVO全称KeyValueObserving,是苹果提供的一套事件通知机制。允许对象监听另一个对象特定属性的改变,并在改变时接受到事件。一般继承自NSObject的对象默认都支持KVO
KVONSNotificationCenter都是iOS观察者模式的一种实现。区别在于,相对于被观察者和观察者之间的关系,KVO是一对一的,而不是一对多的。KVO对被监听的对象没有侵入性,不需要修改其内部代码即可实现监听。

基础使用

使用KVO分为三个步骤:

  1. 通过addObserver:forKeyPath:options:context:方法注册观察者
  2. 在观察者中实现observeValueForKeyPath:ofObject:change:context:方法,当keyPath属性发生变化时,KVO会调用这个方法通知观察者。
  3. 当观察者不需要监听时,调用removeObserver:forKeyPath:方法将KVO移除。需要注意的是,调用removeObserver需要再观察者消失以前,否则会导致Crash。
    简单调用举例:
@interface Person : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSUInteger age;

@end

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    self.p = [[Person alloc]init];
    self.p.name = @"ios";
    self.p.age = 10;
    [self.p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
    self.p.name = @"andriod";
    self.p.age = 8;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"%@",change);
    NSLog(@"new = %@",change[NSKeyValueChangeNewKey]);
    NSLog(@"old = %@",change[NSKeyValueChangeOldKey]);
}

输出结果为:

2019-04-16 21:49:29.834536+0800 KVO原理[36234:11195564] {
    kind = 1;
    new = andriod;
    old = ios;
}
2019-04-16 21:49:29.834709+0800 KVO原理[36234:11195564] new = andriod
2019-04-16 21:49:29.834837+0800 KVO原理[36234:11195564] old = ios
KVO的简单优化

当我们把self.p.name = @"ios"跟原来的值相同时,会发生什么现象呢?

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    self.p = [[Person alloc]init];
    self.p.name = @"ios";
    self.p.age = 10;
    [self.p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
    self.p.name = @"ios";
    self.p.age = 8;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"%@",change);
    NSLog(@"new = %@",change[NSKeyValueChangeNewKey]);
    NSLog(@"old = %@",change[NSKeyValueChangeOldKey]);
}

打印输出结果:

2019-04-16 22:09:47.840181+0800 KVO原理[36296:11226374] {
    kind = 1;
    new = ios;
    old = ios;
}
2019-04-16 22:09:47.840368+0800 KVO原理[36296:11226374] new = ios
2019-04-16 22:09:47.840455+0800 KVO原理[36296:11226374] old = ios

发现无论是值是否变化,只要调用了setter方法,都会得到通知,这样可能会造成不必要的资源浪费。我们做一个简单的小优化:只有重新赋值(不一样的值)才去通知观察者,这就需要引入新的知识了
willChangeValueForKey:
didChangeValueForKey:
当我们手动控制通知消息发送的时机是,就需要用到这两个函数,顾名思义,函数的作用就是 key value即将变化, key value已经变化
另外一个函数:+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key是否自动通知观察者,如果需要手动控制,则需要返回NO,自动控制的话返回YES
我们将程序做一个简单的修改:

//Person.m
- (void)setName:(NSString *)name {
    if (![_name isEqualToString:name]) {
        [self willChangeValueForKey:@"name"];
        _name = name;
        [self didChangeValueForKey:@"name"];
    }
}

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
    BOOL automatic = NO;
    if ([key isEqualToString:@"age"]) {
        automatic = YES;
    } else if ([key isEqualToString:@"name"]) {
        automatic = NO;
    }  else {
        automatic = [super automaticallyNotifiesObserversForKey:key];
    }
    return automatic;
}

此时我们发现,当再次给p.name赋值为原来的值时,观察者不再收到通知事件。

总结

KVO是在setter函数调用时,给观察者发送事件通知,铭记在观察者释放之前移除KVO
至于为什么调用setter函数,就会给观察者发送时间通知?我们下次分享KVO的实现原理。

参考资料:
KVO原理分析及使用进阶
KVO进阶(二)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值