KVO 鍵值觀察
阿新 • • 發佈:2018-11-15
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"]; }