KVO的使用

技术概述

KVO全称KeyValueObserving,是苹果提供的一套事件通知机制。允许对象监听另一个对象特定属性的改变,并在改变时接收到事件。由于KVO的实现机制,所以对属性才会发生作用,一般继承自NSObject的对象都默认支持KVO。

技术详述

基础使用

  1. 通过addObserver:forKeyPath:options:context:方法注册观察者,观察者可以接收keyPath属性的变化事件。
    在观察者中实现

  2. observeValueForKeyPath:ofObject:change:context:方法,当keyPath属性发生改变后,KVO会回调这个方法来通知观察者。

  3. 当观察者不需要监听时,可以调用removeObserver:forKeyPath:方法将KVO移除。需要注意的是,调用removeObserver需要在观察者消失之前,否则会导致Crash。

注册方法

在注册观察者时,可以传入options参数,参数是一个枚举类型。如果传入NSKeyValueObservingOptionNew和NSKeyValueObservingOptionOld表示接收新值和旧值,默认为只接收新值。如果想在注册观察者后,立即接收一次回调,则可以加入NSKeyValueObservingOptionInitial枚举。

还可以通过方法context传入任意类型的对象,在接收消息回调的代码中可以接收到这个对象,是KVO中的一种传值方式。

在调用addObserver方法后,KVO并不会对观察者进行强引用,所以需要注意观察者的生命周期,否则会导致观察者被释放带来的Crash。

监听方法

观察者需要实现observeValueForKeyPath:ofObject:change:context:方法,当KVO事件到来时会调用这个方法,如果没有实现会导致Crash。change字典中存放KVO属性相关的值,根据options时传入的枚举来返回。枚举会对应相应key来从字典中取出值,例如有NSKeyValueChangeOldKey字段,存储改变之前的旧值。

change中还有NSKeyValueChangeKindKey字段,和NSKeyValueChangeOldKey是平级的关系,来提供本次更改的信息,对应NSKeyValueChange枚举类型的value。例如被观察属性发生改变时,字段为NSKeyValueChangeSetting。

如果被观察对象是集合对象,在NSKeyValueChangeKindKey字段中会包含NSKeyValueChangeInsertion、NSKeyValueChangeRemoval、NSKeyValueChangeReplacement的信息,表示集合对象的操作方式。

实际应用

KVO主要用来做键值观察操作,想要一个值发生改变后通知另一个对象,则用KVO实现最为合适。

#import <Foundation/Foundation.h>
 
@interface Book : NSObject
 
@property (nonatomic,strong)NSString *name;
@property (nonatomic,strong)NSString *price;
 
@end
#import "ViewController.h"
#import "Book.h"
 
@interface ViewController ()
 
@property (nonatomic,strong)Book *abook;
 
@end
 
@implementation ViewController
 
- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self addObserver];
    [self addBtn];
 
}
 
 
/**
 添加监听
 */
-(void)addObserver{
    
    //添加监听
    self.abook = [[Book alloc]init];
    self.abook.price = @"0";//先设一个初始值
    [_abook addObserver:self forKeyPath:@"price" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
    
}
 
 
/**
 添加一个按钮
 */
-(void)addBtn{
    
    UIButton *abtn = [UIButton buttonWithType:UIButtonTypeCustom];
    abtn.frame = CGRectMake(80, 90.0, 80, 30);
    [abtn setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
    [abtn setTitle:@"Change" forState:UIControlStateNormal];
    [abtn addTarget:self action:@selector(btnClick) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:abtn];
    
}
 
 
/**
 按钮点击事件
 */
-(void)btnClick{
    
    NSLog(@"点击了Btn!");
    NSInteger randomPrice = arc4random() % 100;
    NSString *newPrice = [NSString stringWithFormat:@"%ld",(long)randomPrice];
    
    //触发监听
    //第一种方法
//    NSDictionary *newBookPropertiesDictionary=[NSDictionary dictionaryWithObjectsAndKeys:
//                                               @"book name",@"name",
//                                               newPrice,@"price",nil];
//    [self.abook setValuesForKeysWithDictionary:newBookPropertiesDictionary];
    
    //第二种方法
    [self.abook setValue:newPrice forKey:@"price"];
 
  不仅可以通过点语法和set语法进行调用,KVO兼容很多种调用方式。
 
 
}
 
//实现监听
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if ([keyPath isEqual:@"price"]) {
        NSLog(@"old price: %@",[change objectForKey:@"old"]);
        NSLog(@"new price: %@",[change objectForKey:@"new"]);
    }
}
 
-(void)dealloc
{
    //移除监听
    [_abook removeObserver:self forKeyPath:@"price"];
}
 
 
@end

打印结果

KVOTest[40935:3327447] 点击了Btn!
KVOTest[40935:3327447] old price: 0
KVOTest[40935:3327447] new price: 87
KVOTest[40935:3327447] 点击了Btn!
KVOTest[40935:3327447] old price: 87
KVOTest[40935:3327447] new price: 49

总结

  1. KVO在使用时添加观察者和移除观察者应到成对出现
  2. 被观察者在销毁前应当移除所有的观察者,iOS10以下会崩溃,iOS11以上不会崩溃,坑点!
  3. 一个对象如果作为观察者,在该对象dealloc前应当被移除,否则会导致崩溃

参考文献

KVO的使用及底层实现

原文地址:https://www.cnblogs.com/MingLL/p/13183294.html