KVO

添加觀察方法:數組

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

相關文章
相關標籤/搜索