原理篇-KVO

    KVO可能你們都比較熟了,網上的文章也有不少~我第一次把本身對它的理解整理出來~歡迎你們來找茬~bash

介紹

KVO是一種機制,當對象(被觀察)的某個屬性發生更改時,對象能夠得到通知,並做出相應處理。那麼他是怎麼監聽的呢?ui

原理

KVO是用了isa-swizzling來實現的。當對象被kvo觀察的時候,此對象的isa指針會改變,指向一箇中間的類,而不是它真正的類。而後重寫setter方法。spa

原理的證實

被kvo監聽的對象,isa指針指向的中間類是怎樣的?

//一、建立一個Person類,屬性變量有age
    self.p2 = [[Person alloc]init];
    self.p2.age = 1;
    
    //二、輸出結果:====p2未監聽前,isa指針Person
    NSLog(@"====p2未監聽前,isa指針%@", object_getClass(self.p2));
    
    //三、p2的age屬性添加監聽
    [self.p2 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew| NSKeyValueObservingOptionOld context:nil];
    
    //四、輸出結果:====p2監聽後,isa指針NSKVONotifying_Person
    NSLog(@"====p2監聽後,isa指針%@", object_getClass(self.p2));
    
    //五、移除監聽
    [self.p2 removeObserver:self forKeyPath:@"age" context:nil];
    
    //六、輸出結果:====p2移除監聽後,isa指針Person
    NSLog(@"====p2移除監聽後,isa指針%@", object_getClass(self.p2));
複製代碼

由上段代碼能夠看出,p2被監聽前,isa指針指向Person類。在添加監聽後,isa指針指向了NSKVONotifying_Person類。指針

所以,不能用isa指針來獲取他真正的類,而是經過class方法來獲取。code

中間類和以前的類是什麼關係呢?

將上面的第4步的輸出。多輸出一個isa指針指向的類的父類。server

//====p2監聽後,isa指針NSKVONotifying_Person====isa指針指向的類的父類指針是Person
    NSLog(@"====p2監聽後,isa指針%@====isa指針指向的類的父類指針是%@", object_getClass(self.p2),class_getSuperclass((Class)object_getClass(self.p2)));
複製代碼

由上可看出,生成的中間類NSKVONotifying_Person類是原類Person類的子類。對象

怎麼重寫的setter方法來通知呢?

咱們手動實現一個KVO來證實~rem

將kvo自動發送通知改爲NO
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
    
    BOOL automatic = NO;
    if ([theKey isEqualToString:@"age"]) {
        automatic = NO;
    }
    else {
        automatic = [super automaticallyNotifiesObserversForKey:theKey];
    }
    return automatic;
}
複製代碼

此時,再跑一下程序,會發現當咱們改變age的時候,不能再收到通知。也就是如下方法,不會被調用。get

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"%@的%@改變了%@", object, keyPath, change);
}
複製代碼
手動發送通知

在Person類中重寫setter方法string

- (void)setAge: (NSInteger)age {
    
    //在改變值前調用
    [self willChangeValueForKey:@"age"];
    _age = age;
    //在改變值以後調用
    [self didChangeValueForKey:@"age"];
}
複製代碼

OK,在重寫setter方法以後,咱們又能收到通知了~若是咱們想只有在年齡值改變的時候,才收到通知。那咱們能夠將setter方法改爲以下:

- (void)setAge: (NSInteger)age {
    
    if (_age != age) {
        //在改變值前調用
        [self willChangeValueForKey:@"age"];
        _age = age;
        //在改變值以後調用
        [self didChangeValueForKey:@"age"];
    }
}
複製代碼

這樣,在咱們給age賦值和上次同樣的時候,咱們不會再收到通知。也就是說下面這種狀況只會收到兩次通知。

self.p2.age = 100;
    self.p2.age = 110;
    self.p2.age = 110;
複製代碼

咱們再給person加個屬性變量birthYear,當age變的時候,birthYear跟着變化。那麼setter方法能夠寫成下面這樣

- (void)setAge: (NSInteger)age {
    
    if (_age != age) {
        //在改變值前調用
        [self willChangeValueForKey:@"age"];
        [self willChangeValueForKey:@"birthYear"];
        NSInteger gap = age - _age;
        _age = age;
        _birthYear = _birthYear + gap;
        //在改變值以後調用
        [self didChangeValueForKey:@"age"];
        [self didChangeValueForKey:@"birthYear"];
    }
    
}
複製代碼

這樣,當age變的時候,birthYear也會跟着相應變化。

最後

期待大佬們能夠友情的提出更深一層的問題,哈哈

相關文章
相關標籤/搜索