iOS:KVO 實現觀察者模式

文章結構

前言

在 iOS 開發中,常常需要在不同的對象、不同的視圖(View)或不同的視圖控制器(ViewController)之間通信,傳遞數據。主要的實現方法有:

  • 直接通過 superView 或 subView 傳遞數據,或者在類中添加其他對象的引用。方法直接但效率低、容易使代碼混亂,難以處理復雜的關系。
  • 通過自帶的或自定義的 delegate 協議通信。效率較高,能完成復雜的通信及執行復雜的操作,代碼結構較好,但是代碼量比較大。
  • 使用 KVO(Key-value observing)。能夠穿越復雜的關系網,直接觀察其他對象的屬性,獲取信息,根據所觀察對象的變化進行響應,代碼量少。

這些方法各有優劣,在不同的情況下選用合適的方法是最好的。因此掌握這些方法,才能更好地應對各種開發難題。KVO 是本文關注的重點。

KVO 簡介

在 Apple 的應用開發里 KVO 提供了一個途徑,使對象(觀察者)能夠觀察其他對象(被觀察者)的屬性,當被觀察者的屬性發生變化時,觀察者就會被告知該變化。這其實就對應設計模式中的觀察者模式。

觀察者:Observer,the observing object;被觀察者:the observed object

前提條件

在實現 KVO 之前,需要確保被觀察的對象是支持 KVO 的。通常繼承自 NSObject 的對象都會自動支持 KVO。對于非繼承自 NSObject 的類,也可以手動實現 KVO 支持

適用場景

KVO 能很方便地實現模型(Model)和控制器(Controller)之間的通信。主要的應用場景有:

KVO 能夠實現一對多、多對多、多對一的觀察。也就是說,KVO 沒有限制觀察者和被觀察者的數量。當同時觀察多個對象時,不但對象本身發生改變時會告知觀察者,而且被觀察對象發生替換、刪除或插入等操作時也會告知觀察者。

實現 KVO:注冊觀察者和觀察者方法

基本流程

  1. 添加觀察者:addObserver:forKeyPath:options:context:
  2. 實現觀察響應方法:observeValueForKeyPath:ofObject:change:context:
  3. 在觀察者 deallocted 之前移除觀察者: removeObserver:forKeyPath:

添加觀察者

observedObject.addObserver:forKeyPath:options:context:

注意:

  • 調用該函數的 observedObject 是被觀察者,參數 addObserver 后面的是觀察者;
  • forKeyPath 參數是 String 類型的,代表 observedObject 的屬性,私有屬性也可以觀察,但是在 Swift 中需要把被觀察對象的屬性用 dynamic 標記,如:
class ObservedObjectClass: NSObject {
//在 Swift 中要用 dynamic 標記被觀察的屬性
    dynamic private var observedProperty = ""
    ...
}
  • options:可以選擇獲取的數據包含哪些內容,獲取的數據是以字典的形式傳遞的。

    • NSKeyValueObservingOptionOld: 獲取變化前的數據
    • NSKeyValueObservingOptionNew: 獲取變化后的數據
    • NSKeyValueObservingOptionInitial: 獲取設置觀察者時被觀察者的初始數據,即在 addObserver 函數調用完成前,被觀察者的數據。
    • NSKeyValueObservingOptionPrior: 在變化前后分別發送消息(共發送兩次消息)
  • context:可選的參數,會隨著觀察消息傳遞,用于區分接收該消息的觀察者。一般情況下,只需通過 keyPath 就可以判斷接收消息的觀察者。但是當父類子類都觀察了同一個 keyPath 時,僅靠 keyPath 就無法判斷消息該傳給子類,還是傳給父類。

  • addObserver 并不會維持對觀察者、被觀察者和 Context 的強引用。如果需要的話,要自行維持對它們的強引用。

觀察響應方法:

所有的觀察者都必須實現觀察響應方法:
observeValueForKeyPath:ofObject:change:context:

  • change 是一個字典,包含了一系列鍵-值。

    • NSKeyValueChangeKindKey: 變化的類型
    • NSKeyValueChangeOldKey: 變化前的值
    • NSKeyValueChangeNewKey: 變化后的值
    • NSKeyValueChangeIndexesKey: 在所有變化中的坐標
  • NSKeyValueChangeKindKey 又包含了:

    • NSKeyValueChangeSetting
    • NSKeyValueChangeInsertion
    • NSKeyValueChangeRemoval
    • NSKeyValueChangeReplacement
  • 如果接收到的觀察者消息于當前的 Context 不符,就需要把消息傳給 父類,直到尋找到對應的 Context。

  • 如果一個消息傳到了 NSObject 仍然沒有找到它的觀察者,那么就會拋出異常:NSInternalInconsistencyException。

移除觀察者

當一個對象不再需要觀察另一個對象時,就需要移除觀察。

observedObject.removeObserver:forKeyPath:context:

這個方法和添加觀察者的方法是對應的。

移除觀察者需要注意以下幾點:

  • 一個對象如果沒有注冊成為觀察者,那么當調用 removeObserver 移除它時,就會拋出異常。所以想要安全地移除觀察者,可以使用 do、try、catch 來調用 removeObserver。
  • 觀察者不會在 dealloc 的時候自動移除。因此最晚必須在觀察者 dealloc 時移除它。
  • 系統沒有自帶的方法用于判斷一個對象是否注冊為觀察者,因此盡量在初始化的時候注冊觀察者,在 dealloc 時移除。

總結

KVO 能夠在復雜的關系網中直接觀察某個對象,合理的使用 KVO 能夠簡化代碼。但是 KVO 也有很多坑,稍有不慎就會拋出異常或者無法建立觀察。在實踐中,還是應該選擇合適的方法來完成對象間的通信,熟練應對各種情況。

歡迎訪問我的Github:LinShiwei (Lin Shiwei) · GitHub

有任何疑問的話,歡迎在下方評論區討論。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容