1. 程式人生 > >KVO 鍵值觀察

KVO 鍵值觀察

1、KVO

  • KVO的使用

  • KVO 是 Key-Value Observing 的簡寫,是鍵值觀察的意思,屬於 runtime 方法。Key Value Observing 顧名思義就是一種 observer 模式用於監聽屬性變數值的變化,也是執行時的方法,當例項變數改變時,系統會自動採取一些動作。KVO 跟 NSNotification 有很多相似的地方,用 addObserver:forKeyPath:options:context: 去 start observer, 用 removeObserver:forKeyPath:context 去 stop observer, 回撥就是 observeValueForKeyPath:ofObject:change:context:。
  • 對於 KVO 來說,我們要做的只是簡單 update 我們的 property 的資料,不需要像 NSNotificationCenter 那樣關心是否有人在監聽你的請求,如果沒有人監聽該怎麼辦,所有 addObserver, removeObserver, callback 都是想要監聽的你的 property 的 class 做的事情。曾經做個專案,用 NSNotificationCenter post Notification 在一個 network callback 裡面,可是這時候因為最早的 addObserver 的 class 被釋放了,接著生成的 addObserver 的 class, 就接受到了上一個 observer 該監聽的事件,所以造成了錯誤,那時候的解決方案是為 addObserve key 做 unique,不會 2 次 addObserver 的 key 是相同的,但是有了 KVO, 我們同樣可以用 KVO 來完成,當 addOberver 的的 object remove 的時候,就不會有這樣的 callback 被呼叫了。
  • KVO 給我們提供了更少的程式碼,和 NSNotification 比好處,不需要修改被觀察的 class, 永遠都是觀察你的人做事情。 但是 KVO 也有些毛病:
    • 1、如果沒有 observer 監聽 keyPath, removeObsever:forKeyPath:context: 這個 keyPath, 就會 crash(崩潰), 不像 NSNotificationCenter removeObserver。
    • 2、對程式碼你很難發現誰監聽你的 property 的改動,查詢起來比較麻煩。
    • 3、對於一個複雜和相關性很高的 class,最好還是不要用 KVO, 就用 delegate 或者 notification 的方式比較簡潔。
  • KVO 使用分三步:
    • 1、註冊成為觀察者。
    • 2、觀察者定義 KVO 的回撥。
    • 3、移除觀察者。
  • KVO 使用注意:
    • KVO 是同步的,一旦物件的屬性發生變化,只有用同步的方式,才能保證所有觀察者的方法能夠執行完成。KVO 監聽方法中,不要有太耗時的操作。
    • KVO 的方法呼叫,是在對應的執行緒中執行的。在子執行緒修改觀察屬性時,觀察者回調方法將在子執行緒中執行。
    • 在多個執行緒同時修改一個觀察屬性的時候,KVO 監聽方法中會存在資源搶奪的問題,需要使用互斥鎖。如果涉及到多執行緒,KVO 要特別小心,通常 KVO 只是做一些簡單的觀察和處理,千萬不要搞複雜了,KVO的監聽程式碼,一定要簡單。
    • 一定要刪除觀察者,如果不刪除觀察者,釋放物件,會直接崩潰。An instance 0x7fd340ebc400 of class KvoClass was deallocated while key value observers were still registered with it.

2、KVO 的使用

// KvoClass.h
@interface KvoClass : NSObject
@property(nonatomic, copy) NSString *name;
@end
// ViewController.m
@property(nonatomic, retain) KvoClass *kvoObject;
_kvoObject = [[KvoClass alloc] init];
  • 2.1 KVO 新增

    - (void)addObserver:(NSObject *)observer
             forKeyPath:(NSString *)keyPath
                options:(NSKeyValueObservingOptions)options
                context:(nullable void *)context;
    public func addObserver(observer: NSObject,
                  forKeyPath keyPath: String,
                             options: NSKeyValueObservingOptions,
                             context: UnsafeMutablePointer<Void>)
    引數說明:
       第一個引數 observer 是觀察的類;
       第二個引數 keyPath 是被觀察的類中被觀察的屬性;
       第三個引數 options 是觀察選項;
       第四個引數 context 是傳遞的上下文內容。
    
    // 新增觀察者
    [_kvoObject addObserver:self
                 forKeyPath:@"name"
                    options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
                    context:@"nameChange"];
    // 改變被觀察的鍵對應的值
    _kvoObject.name = @"xiao bai";
    sleep(2);
    _kvoObject.name = @"xiao hei";
  • 2.2 KVO 回撥

    - (void)observeValueForKeyPath:(nullable NSString *)keyPath
                          ofObject:(nullable id)object
                            change:(nullable NSDictionary<NSString*, id> *)change
                           context:(nullable void *)context;
    public func observeValueForKeyPath(keyPath: String?,
                               ofObject object: AnyObject?,
                                        change: [String : AnyObject]?,
                                       context: UnsafeMutablePointer<Void>)
    引數說明:
        keyPath:監控的 key;
        object:被監控的物件的基本屬性;
        change:被監控的物件的 key 對應的 value 值的變化(kind:型別,new:變化後的值,old:變化前的值。
    
    // 系統自帶方法
    - (void)observeValueForKeyPath:(NSString *)keyPath
                          ofObject:(id)object
                            change:(NSDictionary *)change
                           context:(void *)context {
        if (context == @"nameChange") {
            NSLog(@"name 值被改變 kind = %@, oldValue = %@, newValue = %@",
                         change[@"kind"], change[@"old"], change[@"new"]);
        } else {
            [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
        }
    }
  • 2.3 KVO 移除

    • 在實際工作中需要在合適的時候移除觀察者身份。
    NS_AVAILABLE(10_7, 5_0)
    - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context;
    - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
    public func removeObserver(observer: NSObject, forKeyPath keyPath: String, context: UnsafeMutablePointer<Void>)
    public func removeObserver(observer: NSObject, forKeyPath keyPath: String)
    引數說明:
        第一個引數 observer 是觀察的類;
        第二個引數 keyPath 是被觀察的類中被觀察的屬性;
        第三個引數 context 是傳遞的上下文內容。
    
    - (void)dealloc {
        // 移除觀察者
        [_kvoObject removeObserver:self forKeyPath:@"name" context:@"nameChange"];
    }