添加觀察方法:數組
- (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方法裏實現了通知機制,因此調用點語法才能觸發通知。