技术概述
KVO全称KeyValueObserving,是苹果提供的一套事件通知机制。允许对象监听另一个对象特定属性的改变,并在改变时接收到事件。由于KVO的实现机制,所以对属性才会发生作用,一般继承自NSObject的对象都默认支持KVO。
技术详述
基础使用
-
通过addObserver:forKeyPath:options:context:方法注册观察者,观察者可以接收keyPath属性的变化事件。
在观察者中实现 -
observeValueForKeyPath:ofObject:change:context:方法,当keyPath属性发生改变后,KVO会回调这个方法来通知观察者。
-
当观察者不需要监听时,可以调用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
@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
总结
- KVO在使用时添加观察者和移除观察者应到成对出现
- 被观察者在销毁前应当移除所有的观察者,iOS10以下会崩溃,iOS11以上不会崩溃,坑点!
- 一个对象如果作为观察者,在该对象dealloc前应当被移除,否则会导致崩溃
参考文献
KVO的使用及底层实现