如果瞭解過設計模式的同學,應該都知道有一種設計模式叫做觀察者模式,屬於行為型模式,即當物件存在一對多的依賴關係,當一個物件發生變化時,需要自動通知它的依賴物件。通常用於實時事件處理。
我們來研究一下 iOS
裡對觀察者模式的支援,即 KVO(key-value observing)
,鍵值對觀察,其原理是基於 KVC(key-value-coding)
和 runtime
。通過 Swfit 研究。
KVO 使用
由於 Swift4
之後 KVO
的 api
有所改變,所以先來看看 Swift4
之前使用 KVO
。
class Test: NSObject {
dynamic var field = "field"
}
複製程式碼
var test = Test()
override func viewDidLoad() {
super.viewDidLoad()
test.addObserver(self, forKeyPath: "field", options: [.new, .initial], context: nil)
test.field = "change"
}
override func observeValue(forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?) {
print(change)
}
deinit {
test.removeObserver(self, forKeyPath: "field")
}
複製程式碼
在Swift4之前使用 KVO
,即需要在 deinit
中呼叫 removeObserver
,否則會crash,還需要重寫 NSObject
的
func observeValue(forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?)
複製程式碼
以完成更多的操作。
而在Swift4中,KVO
的 API 變得友好很多。
@objcMembers class Test: NSObject {
dynamic var field = "field"
}
var observer: NSKeyValueObservation!
override func viewDidLoad() {
super.viewDidLoad()
let test = Test()
observer = test.observe(\.field, options: [.new, .initial]) { (object, change) in
print(change)
}
test.field = "change"
}
複製程式碼
通過閉包優化了 KVO
的實現,在官方WWDC視訊上What's New in Foundation專題上介紹 KVO
時,表示該函式會返回一個 NSKeyValueObservation
,所以只需要管理這個例項的生命週期,不再需要移除觀察者,再也不用擔心忘記移除觀察者而導致crash。(ps: 可以嘗試在方法內宣告 NSKeyValueObservation
物件,可以發現即使改變了屬性值也不會呼叫閉包內的操作。 因為隨著方法的結束,這個例項和閉包的生命週期都結束了。)
在閉包中第一個引數是對被觀察者的引用,防止在閉包內使用被觀察者而導致迴圈引用的問題(相當nice)。
遺憾的是隻有繼承於 NSObject
的物件才能夠使用 KVO
。
KVO 原理
Swift 4 之前
採用斷點除錯
addObserver
之前
addObserver
之後
可見,在 addObserver
之後, test
例項會將isa指標指向 NSKVONotifying_Test
的派生類
再通過runtime來具體看看這個兩個類的區別
採用輔助函式,檢視test
類和方法的變化。
func find() {
print(NSStringFromClass(object_getClass(test)!))
print(String(describing: class_getName(object_getClass(test))))
var count: UInt32 = 0
let methodlist = class_copyMethodList(object_getClass(test), &count)
for i in 0..<count {
print(NSStringFromSelector(method_getName(methodlist![Int(i)])))
}
print("\n")
}
複製程式碼
addObserver
之前
addObserver
之後
從上圖輸出結果可見,在 addObserver
之後類發生了改變,並且新增了一個私有屬性 _isKVOA
,從名字可以推測是用於對類標示,以此來標示是 KVO
。
從圖中可以看出 NSKVONotifying_Test
重寫了被觀察屬性 field
的 set
方法(即 setField:
)。再來看看具體是怎麼重寫的。
根據 KVO
的 api
有手動呼叫的方法。
func willChangeValue(forKey key: String)
func didChangeValue(forKey key: String)
複製程式碼
可以推測是在 set
方法內新增 func willChangeValue(forKey key: String)
和 func didChangeValue(forKey key: String)
通過堆疊資訊具體看一看呼叫情況。
第一次呼叫時 initial
,第二次是屬性發生變化時呼叫的。從堆疊資訊一目瞭然。
Swift 4 之後
接下來我們來探討下,Swift4
之後 KVO
的新 API
,具體的底層原理。
先根據上面的方法測試下是否是通過 runtime
新增 NSKVONotifying_Test
派生類實現的。
從測試結果可見,與 Swift4
之前的原理一致。但是區別在於 NSKVONotifying_Test
的生命週期由 NSKeyValueObservation
管理,通過斷點除錯看看 NSKeyValueObservation
。
NSKeyValueObservation
內有一個 object
屬性 是指向觀察者 test
和 callback
回撥閉包,以及 path
代指被觀察的屬性。
從堆疊資訊,可以一目瞭然的看到當被觀察屬性發生改變時,呼叫情況。
NSKeyValueObservation
作為了觀察者和訊息轉發者,接收通知和通知 test
的屬性發生改變,從而呼叫 閉包
內的具體操作。
才疏學淺,如有什麼理解不到位的歡迎指出。