添加观察方法:数组
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;
第一个参数是观察者对象,负责处理监听事件;第二个是观察的属性的路径;第三个是观察的选项;第四个是上下文。性能
监听回调方法:测试
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
大部分的参数是和添加方法里的对应的,不一样的是观察选项和change参数,但二者是对应的。ui
options有四个选项编码
NSKeyValueObservingOptionNew:change会接收到观察属性的新值;atom
NSKeyValueObservingOptionOld:change会接收到观察属性的旧值;spa
NSKeyValueObservingOptionInitial:回调方法会在观察属性初始化的时候调用,但不会接收到这个初始值,除非和NSKeyValueObservingOptionNew选项一块儿使用;指针
NSKeyValueObservingOptionPrior:回调方法会触发两次,一次是观察属性改变前,一次是改变后,因此能够配合willChangeValueForKey:方法一块儿使用code
好比说你们都喜欢给宠物取名字,养的猫也会有名字orm
@interface Cat : NSObject @property (nonatomic, strong) NSString* name; @end
咱们想要在猫的名字改变的时候有个通知,咱们在一个viewcontroller里面作测试,其余多余的代码暂时不要
#import "KVOViewController.h" #import "Cat.h" @interface KVOViewController () @property (nonatomic, strong) Cat* whiteCat; @end @implementation KVOViewController - (void)viewDidLoad { [super viewDidLoad]; _whiteCat = [[Cat alloc] init]; _whiteCat.name = @"hello"; [_whiteCat addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil]; } - (IBAction)observerTap:(id)sender { _whiteCat.name = @"kitty"; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([keyPath isEqualToString:@"name"]) { NSLog(@"white cat's name change"); } } @end
咱们在当前进行观察的,因此第一个参数观察者是self,固然这不是固定的,能够指到其余地方观察,当都要实现监听方式。
咱们是要在猫的名字改变的时候获得事件,因此监听的第二个参数keypath是name。
keypath描述的是要观察的属性,而这个属性,要符合KVC协议的,简单点说,就是这个属性要可以被进行如下操做
- (void)setValue:(id)value forKey:(NSString *)key;
就是说这个属性可以被键值编码,若是咱们想观察一个数组的count属性,直接使用
[_obArr addObserver:self forKeyPath:@"count" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
程序会crash的,由于数组的count属性不符合键值编码,即简单的不能被setValue:forKey,由于它不是id类型。
keypath,顾名思义,这是一个寻找的路径,因此这里若是是想观察name的属性也是能够用点语法观察的,固然,前提也是要符合KVO协议。
最后一个参数,通常是传nil或者NULL,但做用呢,其实很强大,好比当前有两个Cat的实例,我都要观察他们的name属性变化,我该如何区分是哪一个实例呢?就用这个参数,context。咱们给上面的例子添加点代码:
@interface KVOViewController () @property (nonatomic, strong) Cat* whiteCat; @property (nonatomic, strong) Cat* blackCat; @end @implementation KVOViewController - (void)viewDidLoad { [super viewDidLoad]; _whiteCat = [[Cat alloc] init]; _whiteCat.name = @"hello"; [_whiteCat addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:@"White"]; _blackCat = [[Cat alloc] init]; _blackCat.name = @"kitty"; [_blackCat addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:@"Black"]; } - (IBAction)observerTap:(id)sender { _whiteCat.name = @"kitty"; _blackCat.name = @"hello"; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([keyPath isEqualToString:@"name"]) { id instanceObj = (__bridge id)(context); NSString* contextStr = instanceObj; if ([contextStr isEqualToString:@"White"]) { NSLog(@"white cat's name change"); } else if ([contextStr isEqualToString:@"Black"]) { NSLog(@"black cat's name change"); } } } @end
若是是本身观察本身的属性变化呢,该如何作?下面是个人实验代码:
@interface Cat : NSObject @property (nonatomic, strong) NSString* name; - (void)beginToObserver; - (void)changeName; @end @implementation Cat - (void)beginToObserver { [self addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil]; } - (void)changeName { _name = @"hello"; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { NSLog(@"here"); if ([keyPath isEqualToString:@"name"]) { NSLog(@"cat's name change"); } } @end
生成了Cat实例后,调用beginToObserver添加观察,在某处触发改变调用changeName改变名字。
运行的结果是,触发了按钮事件,观察的字符串的值也改变了,可是就是不触发监听回调方法。为何?
咱们再修改一处代码,把字符串的再次赋值方式,变成点语法赋值:
- (void)changeName { self.name = @"hello"; }
此次运行就触发了监听回调方法了。
一开始我觉得是set方法才会进行setValue:forKey:,但以为不会这么简单,在这里找到了答案
添加观察先后,isa指针指向发生了改变,这是在KVO经过runtime建立被观察的class的subclass(一般会以NSKVONotifying前缀),在这个subclass里,set方法会被重写,在set方法里实现了通知机制,因此调用点语法才能触发通知。