Key-Value Observing In Cocoa

什麼是KVO

KVO即Key-Value-Observing,鍵值觀察,是觀察者模式的一種實現。KVO提供了一種機制可以方便的觀察對象的屬性。如:指定一個被觀察對象,當對象的某個屬性發生變化時,對象會得到通知,進而能夠作出相應的處理。在實際的開發中對於model與controller之間進行交流是很是有用的。controller一般觀察Model的屬性,view經過controller觀察Model對象的屬性。 然而,Model對象能夠觀察其餘Model對象(一般用於肯定依賴別人的值什麼時候改變)或甚至自身(再次肯定依賴別人的值什麼時候改變)。bash

一個簡單的例子說明了KVO如何在您的應用程序中發揮做用。 假設Person對象與Account對象交互,表示該人在銀行的儲蓄帳戶。 Person的實例可能須要知道Account實例的某些方面什麼時候發生變化,例如餘額或利率。ide

若是這些屬性是Account的公共屬性,那麼Person能夠按期輪詢賬戶以發現更改,但這固然是低效的,而且一般是不切實際的。 更好的方法是使用KVO,相似於當帳戶的屬性值發生更改時Preson接收到一箇中斷。ui

KVO的原理

Automatic key-value observing is implemented using a technique called isa-swizzling.The isa pointer, as the name suggests, points to the object's class which maintains a dispatch table. This dispatch table essentially contains pointers to the methods the class implements, among other data. When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class. As a result the value of the isa pointer does not necessarily reflect the actual class of the instance. You should never rely on the isa pointer to determine class membership. Instead, you should use the class method to determine the class of an object instance.spa

如何使用KVO

KVO的實現依賴於OC強大的Runtime,上面文檔也說道,KVO是經過使用isa-swizzling技術實現的。debug

基本的流程是:當某個類的屬性對象第一次被觀察時,系統會在運行期動態地建立該類的一個子類,在這個子類中重寫父類中任何被觀察屬性的setter方法,子類在被重寫的setter方法內實現真正的通知機制。3d

例如:當 被觀察對象爲A時,KVO機制動態建立一個新類,名爲NSKVONotifying_A。該類繼承自對象A的類,而且重寫了觀察屬性的setter方法,setter方法會負責在調用原setter方法以前和以後,通知全部觀察者對象屬性值的改變狀況。指針

每一個類對象都有一個isa指針,該指針指向當前類,當一個類對象的屬性第一次被觀察,系統會偷偷將isa指針指向動態生成的派生類,從而在給被監聽屬性賦值時執行的是派生類的setter方法。code

子類setter方法:cdn

KVO的鍵值觀察通知機制依賴於NSObject的兩個方法,willChangeValueForKey:和didChangeValueForKey:,在進行存取數值先後分別調用這2個方法(由於咱們所監聽的對象屬性能夠獲取新值和舊值,因此屬性改變先後都會通知觀察者)。被觀察屬性發生改變以前,willChangeValueForKey:方法被調用,當屬性發生改變以後,didChangeValueForKey:方法會被調用,以後observeValueForKey:ofObject:change:context方法也會被調用。這裏要注意:重寫觀察屬性的setter方法是在運行時由系統自動執行的。並且蘋果還重寫了class方法,讓咱們誤認爲是使用了當前類,從而達到隱藏生成的派生類。爲了幫助理解過程,借用圖片一張: server

KVO使用流程

  1. 調用addObserver:forkeypath:options:context方法添加觀察者
  2. 重寫observeValue方法
  3. 記得去除觀察者,調用removeObserver:forkeypath:context方法,OC語言在dealloc方法中執行,Swift在deinit方法中,注意:有添加觀察者才執行去除觀察者,若是沒有添加觀察者,就調用去除觀察者會出現異常,即添加觀察者和去除觀察者兩個操做是一一對應的

通常KVO奔潰的緣由

  1. 被觀察的對象被銷燬掉了,好比:被觀察的對象是一個局部變量
  2. 觀察者被釋放掉了,可是沒有移除監聽。這個是很常見的錯誤,在push、pop等相關操做以後很容易崩潰
  3. 註冊的監聽沒有被移除掉,又重寫進行註冊

Demo

let player = AVPlayer(playerItem: playerItem)
// 第一步:監聽AVPlayer的timeControlStatus
player.addObserver(self, forKeyPath: "timeControlStatus", options: .new, context: nil)

// 第二步:重寫observeValue方法
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
        if keyPath == "timeControlStatus" {
            guard let player = player else { return }
            switch player.timeControlStatus {
            case .playing:
                observerDelegate?.playerStatusChangedAction(status: .playing)
            case .paused:
                observerDelegate?.playerStatusChangedAction(status: .paused)
            default:
                return
            }
    }
    
 // 去除監聽者
 deinit {
        player?.removeObserver(self, forKeyPath: "timeControlStatus")
        DocsLogger.debug("-----deinit-----")
    }
複製代碼

KVO的優缺點

優勢

缺點

KVO的使用場景

相關文章
相關標籤/搜索