OC觀察者模式之KVO的使用與思考

2020-12-17 EAWorld

轉載本文需註明出處:微信公眾號EAWorld,違者必究。

引言:

無論用哪種語言進行軟體開發,我們都會接觸到設計模式,個人認為設計模式存在的意義在於:在某些需求下,採用適合的設計模式,使代碼結構合理,從而提高代碼的可讀性、可擴展性、可移植性,此文將要討論的是OC開發中的一種常用模式之一:觀察者模式之KVO。

KVO俗稱鍵值觀察(key-value observe),鍵值觀察是當被觀察的對象屬性發生改變時,會通知到觀察對象的一種機制。

目錄:

1、KVO的作用

2、KVO的使用方法

3、KVO的實現原理

4、KVO與KVC、代理、通知的區別

5、KVO實現過程中的注意事項

無論用哪種語言進行軟體開發,我們都會接觸到設計模式,個人認為設計模式存在的意義在於:在某些需求下,採用適合的設計模式,使代碼結構合理,從而提高代碼的可讀性、可擴展性、可移植性,此文將要討論的是iOS開發中的一種常用模式之一:觀察者模式之KVO。我們先看下官方文檔給的KVO介紹:

翻譯過來就是:KVO是運用isa混寫技術實現自動觀察鍵值的。isa指針是指向對象的類,本質上是指向類中的方法實現。當一個對象註冊觀察者時,這個對象的isa指針被修改指向一個中間類。永遠不要用isa來判斷一個類的繼承關係,而是應該用class方法來判斷類的實例。

KVO俗稱鍵值觀察(key-value observe),鍵值觀察是當被觀察的對象屬性發生改變時,會通知到觀察對象的一種機制。

1.KVO的作用

1、監聽帶有狀態的基礎控制項,如開關、按鈕等;

2、監聽字符串的改變,當監聽的字符串改變時,來做一些自定義的操作;

3、當數據模型的數據發生改變時,視圖組件能動態的更新,及時顯示數據模型更新後的數據,比如tableview中數據發生變化進行刷新列表操作,監聽 scrollView的contentOffset屬性監聽頁面的滑動.

2.KVO的使用方法

KVO的使用可分為自動監聽和手動監聽。

1.自動監聽

1.1自動監聽操作步驟:

(1)添加觀察者

(2)在觀察者中添加觀察鍵值方法

(3)在dealloc中移除監聽

1.2示例代碼:

創建兩個類ModelA和ModelB,兩個類中都添加屬性「des」,在控制器中,將B添加為A的觀察者。代碼如下:

ModelA中代碼:

ModelB中代碼:

控制器中代碼:

控制器中添加觀察者的方法調用的是如下的類方法:

- (void)addObserver:(NSObject *)observer forKeyPath:(NSString*)keyPath options:(NSKeyValueObservingOptions)options context:(nullablevoid *)context

各個參數說明:

@param observer 被監聽的對象

@param keyPath 被監聽對象的屬性名,不可為空,為空崩潰

@param options 有4種

(1)NSKeyValueObservingOptionNew 把更改之前的值提供給處理方法

(2)NSKeyValueObservingOptionOld 把更改之後的值提供給處理方法

(3)NSKeyValueObservingOptionInitial 把初始化的值提供給處理方法,一旦註冊,立馬就會調用一次。通常它會帶有新值,而不會帶有舊值。

(4)NSKeyValueObservingOptionPrior 分2次調用。在值改變之前和值改變之後

@param context 上下文

上述示例代碼的運行結果如下所示:

2.手動監聽

意思就是說:當某些需要控制監聽過程的場景下,就需要手動監聽,比如:為了儘量減少不必要的觸發通知操作,或者當多個更改同時具備的時候才調用屬性改變的監聽方法。

實現手動監聽的要點主要包括這幾部分:

a.重寫

(BOOL)automaticallyNotifiesObserversForKey:(NSString *)key

b.在set方法中在賦值的前後分別調用

willChangeValueForKey和didChangeValueForKey

2.1實現部分屬性的手動監聽

在animal.h中添加兩個屬性age和name,在animal.m中關閉age的自動監聽功能,其它屬性依然可以自動監聽,在控制其中實現添加按鈕點擊按鈕的時候改變age的值,並觸發監聽方法,代碼如下:

animal類:

要實現類方法 automaticallyNotifiesObserversForKey,並在其中設置對特定的 key 不自動發送通知(返回 NO 即可)。這裡要注意,對其它非手動實現的 key,要轉交給 super 來處理[1,2,3]。

控制器:

當不點擊按鈕的時候,列印結果只列印了name屬性的值:

當點擊按鈕之後,會手動觸發監聽,列印結果如下:

2.2所有屬性都手動監聽(禁止自動監聽)

如果需要禁用該類KVO的話直接automaticallyNotifiesObserversForKey返回NO。

將animal.m中的類方法修改之後:

運行之後不點擊按鈕的話,age和name屬性都不會自動調用監聽方法:

點擊了按鈕之後,只有實現了手動監聽的age屬性調用了監聽方法:

3.KVO的實現原理

當某一個類的實例第一次使用KVO的時候,系統就會在運行期間動態的創建該類的一個派生類,該類的命名規則一般是以NSKVONotifying為前綴,以原本的類名為後綴。並且將原型的對象的isa指針指向該派生類。同時在派生類中重載了使用KVO的屬性的setter方法,在重載的setter方法中實現真正的通知機制,正如前面我們手動實現KVO一樣。這麼做是基於設置屬性會調用setter方法,而通過重寫就獲得了 KVO 需要的通知機制。當然前提是要通過遵循 KVO 的屬性設置方式來變更屬性值,如果僅是直接修改屬性對應的成員變量,是無法實現 KVO 的[4,5]。

4.KVO與KVC、代理、通知的區別

1.與KVC的不同?

KVC,即是指 NSKeyValueCoding,一個非正式的 Protocol,提供一種機制來間接訪問對象的屬性,而不是通過調用Setter、Getter方法等 顯式的存取方式去訪問。KVO 就是基於 KVC 實現的關鍵技術之一。

KVO,即Key-Value Observing,它提供一種機制,當指定的對象的屬性被修改後,對象就會接受到通知。

2.與delegate的不同?

和delegate一樣,KVO和NSNotification的作用都是類與類之間的通信。但是與delegate不同的是:這兩個都是負責發送接收通知,剩下的事情由系統處理,所以不用返回值;而delegate 則需要通信的對象通過變量(代理)聯繫;delegate只是一對一,而這兩個可以一對多。delegate是非常嚴格的語法,需要定義很多代碼。

3.和notification的區別?

notification比KVO多了發送通知的一步。兩者都是一對多,但是對象之間直接的交互,notification明顯得多,需要notificationCenter來做為中間交互。而KVO如我們介紹的,設置觀察者->處理屬性變化,至於中間通知這一環,則隱秘多了,只留一句「交由系統通知」,具體的可參照以上實現過程的剖析。notification的優點是監聽不局限於屬性的變化,還可以對多種多樣的狀態變化進行監聽,監聽範圍廣,例如鍵盤、前後臺等系統通知的使用也更顯靈活方便[6,7]。

5.KVO實現過程中的注意事項

iOS 10以下會有這些情況,iOS11不會出現這些情況,但是為了代碼的嚴謹性,以及以防出現無法預知的錯誤,還是避開這些比較好。

1、添加觀察者次數與remove次數不匹配導致程序崩潰

連續對同一屬性添加觀察者是可以的,但是也要保證在移除觀察者的時候也要移除對應次,不然可能會引發崩潰(iOS11以上不會崩潰)。

當對同一個keypath進行兩次removeObserver時會導致程序crash,這種情況常常出現在父類有一個kvo,父類在dealloc中remove了一次,子類又remove了一次的情況下。不要以為這種情況很少出現!當你封裝framework開源給別人用或者多人協作開發時是有可能出現的,而且這種crash很難發現。不知道你發現沒,目前的代碼中context欄位都是nil,那能否利用該欄位來標識出到底kvo是superClass註冊的,還是self註冊的?我們可以分別在父類以及本類中定義各自的context字符串,比如在本類中定義context為@"ThisIsMyKVOContextNotSuper";然後在dealloc中remove observer時指定移除的自身添加的observer。這樣iOS就能知道移除的是自己的kvo,而不是父類中的kvo,避免二次remove造成crash[8]。

2、移除不存在的觀察者(iOS11以上不會崩潰)

當某個對象並沒有添加觀察者時,卻執行了移除觀察者的操作,也會導致程序崩潰,此處不附相關代碼。

3、被觀察者銷毀時還存在觀察者(iOS11以上不會崩潰)

這種情況常出現在複雜邏輯下,觀察者先於被觀察者銷毀[9]

4、KVO 行為是同步的,並且發生與所觀察的值發生變化的同樣的線程上。沒有隊列或者 Run-loop 的處理。手動或者自動調用 -didChange… 會觸發 KVO 通知。

所以,當我們試圖從其他線程改變屬性值的時候我們應當十分小心,除非能確定所有的觀察者都用線程安全的方法處理 KVO 通知。通常來說,我們不推薦把 KVO 和多線程混起來。如果我們要用多個隊列和線程,我們不應該在它們互相之間用 KVO[10]。

參考資料:

[1]https://www.jianshu.com/p/d447660bed7e

[2]https://www.jianshu.com/p/91c41292b5b9

[3]https://www.jianshu.com/p/5a1c58aacb23

[4]https://www.cnblogs.com/yang-shuai/p/8556326.html

[5]https://www.cnblogs.com/PSSSCode/p/5506577.html

[6]http://www.mamicode.com/info-detail-515516.html

[7]https://www.songma.com/news/txtlist_i38955v.html

[8]https://www.cnblogs.com/wengzilin/p/4346775.html

[9]https://segmentfault.com/a/1190000016896055

[10]https://www.jianshu.com/p/b9f020a8b4c9

相關焦點

  • 觀察者模式解析
    ❝設計模式想必大家都懂一些,不僅能體現平時設計寫代碼的基本功,而且也是面試時的高頻考點。今天來講解學習下 「觀察者模式」。本文在講解具體模式的同時,也會列舉 jdk 以及 常用框架中使用到的地方,幫助大家加深理解。
  • 設計模式之觀察者模式(一)
    前面兩篇已經帶大家走進了設計模式的世界,了解了策略模式,還有基本的OO基礎和OO原則,不知道你是否能讀懂以及了解呢。接下來,我們就要進入第二個模式的學習了,觀察者模式,讓我們來一窺究竟吧。觀察者模式是JDK中使用最多的模式之一,可以幫你的對象知悉情況,不會錯過該對象感興趣的事。對象甚至在運行時可決定是否要繼續被通知。並且後續還會一併介紹一對多關係,以及鬆耦合。
  • 觀察者模式
    當對象間存在一對多關係時,則使用觀察者模式(Observer Pattern)。比如,當一個對象被修改時,則會自動通知它的依賴對象。
  • 觀察者模式--設計模式(一)
    (發布訂閱)模式,因此在弄清開始SpringBoot的事件監聽機制的源碼分析前,先來學習下觀察者模式,嘿嘿。23,什麼是觀察者模式觀察者模式(又被稱為發布-訂閱(Publish/Subscribe)模式,屬於行為型模式的一種,它定義了一種一對多的依賴關係,讓多個觀察者對象同時監聽某一個主題對象。這個主題對象在狀態變化時,會通知所有的觀察者對象,使他們能夠自動更新自己。
  • C++設計模式:Observer(觀察者模式)
    假設這樣的需求:我們需要實現一個GUI程序,這個程序用來展示文件分割的進度.但是文件的分割進度可能會有不同的表示,比如我們經常看到的進度處理有:進度條展現百分比展現##號展現我們需要未來能支持到不同的通知模式.代碼架構:主程序(此代碼為了模仿GUI程序中的主窗口):
  • 五分鐘學會觀察者模式
    介紹觀察者模式:多個觀察者同時監聽一個主題對象,當主題對象發生改變時,它的所有觀察者都會收到通知。例如微信公眾號,當作者發文時,所有的訂閱者都會收到。這樣觀察者模式就能實現廣播,同時符合開閉原則,增加新的觀察者不用改原有的代碼。觀察者模式的UML圖如下
  • 觀察者模式的Java實現及應用
    作者:Turwewww.jianshu.com/p/1025f644f100觀察者模式定義觀察者模式定義了對象之間的一對多依賴,這樣一來,當一個對象改變狀態時,它的所有依賴者都會收到通知並自動更新。再比如Android中的EventBus,Rxjava的實現都是基於觀察者模式的思想。再比如回調函數:Android中對Button的點擊監聽等等。觀察者模式可以用來解耦自己用代碼實現一個觀察者模式現在我們用代碼來實現上面訂閱報紙的例子:NewProvider作為對於報社的抽象,每隔兩秒鐘向用戶發送報紙;User作為用戶的抽象,可以收到報紙。
  • 觀察者模式的 Java 實現及應用
    觀察者模式定義觀察者模式定義了對象之間的一對多依賴
  • Gof23種設計模式(20)——觀察者模式
    1 基礎知識1.1 標準定義觀察者(Observer)模式標準定義:定義對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時, 所有依賴於它的對象都得到通知並被自動更新。一個object可以有多個Observer,當—個object對象的狀態發生改變時,所有依賴於它的Observer對象都得到通知被自動更新。觀察者(Observer)模式結構如圖1所示,其角色包括抽象主題(Subject)角色、抽象觀察者(Observer)角色、具體主題(Concrete Subject)角色和具體觀察者(Concrete Observer)角色。
  • 觀察者模式和訂閱發布模式是一樣的嗎?
    看到一篇介紹關於觀察者模式和訂閱發布模式的區別的文章,看完後依然認為它們在概念和思想上是統一的,只是根據實現方式和使用場景的不同,叫法不一樣,不過既然有區別,就來探究一番,加深理解。訂閱發布模式該模式理解起來和觀察者模式一樣,也是定義一對多的依賴關係,對象狀態改變後,通知給所有關心這個狀態的訂閱者。
  • 從觀察者模式到響應式的設計原理
    響應式對使用過 Vue 或 RxJS 的小夥伴來說,應該都不會陌生。響應式也是 Vue 的核心功能特性之一,因此如果要想掌握 Vue,我們就必須深刻理解響應式。接下來阿寶哥將從觀察者模式說起,然後結合 observer-util 這個庫,帶大家一起深入學習響應式的原理。
  • KVO實現原理分析
    在開發過程中,很多時候會用到KVO鍵值觀察,它能夠很輕鬆地去監聽某對象屬性的變化,監聽一些帶狀態的控制項的狀態變化,字符串的改變等等,今天就來探討一下KVO的使用及實現原理。要使用KVO,首先必須確保觀察到的對象符合KVO。通常如果某對象繼承於NSObject並以通常的方式創建屬性,則對象及其屬性將自動符合KVO標準。KVO的使用方式很簡單:首先使用addObserver:forKeyPath:options:context: 方法將觀察者註冊到被觀察對象。
  • 「觀察者效應」-做自己思維的觀察者
    大家都聽說過,雌螳螂交配之後會把雄螳螂吃掉的故事。觀察者可以是別人,也可以是自己,自己的想法、思考、思想和思維。其實我們一直都處在自己大腦或思維的控制之下,生活在焦慮當中。大腦或思維是我們自己的觀察者卻也是我們自己的控制者。
  • 說完觀察者和發布訂閱模式的區別,面試官不留我吃飯了
    我想了想我比較熟悉的:Java 集合類、JVM、多線程、spring 全家桶,我如果說這些肯定會被架構師鄙視,差點忘了我還有一個殺手鐧:設計模式。我拍了拍胸脯:架構師你好,我之前參與項目重構用到了很多設計模式,要不你問我設計模式的東西吧。架構師:那你說說觀察者模式和發布訂閱模式的區別?
  • KVO的實現原理與具體應用
    概念部分旨在剖析 KVO 這一設計模式的實現原理;應用部分通過創建的項目,以說明 KVO 技術在 iOS 開發中所帶來的作用;如果是作為剛接觸 KVO 的初學者,可以在了解第一部分的基本原理後粗略看幾遍底層實現原理,再認真閱讀第二部分的應用內容「學會」怎麼去使用 KVO,往後再慢慢深入了解 KVO 這一「黑魔法」技術的實現原理。
  • 九型人格之觀察者
    觀察者從你那超然的立場來看,很多的活動都是毫無意義的。人們為什麼要到處奔波?為什麼要把精力浪費在微不足道的工作上?早點兒退休靜心思考多好啊!隱私對於你來說至關重要,你特別喜歡退到一個安全的地方,徹底遠離外界的侵擾。你崇尚一切從簡。
  • 《彩虹六號:圍攻(Rainbow Six:Siege)》單人模式和觀察者模式_遊俠...
    《彩虹六號:圍攻(Rainbow Six:Siege)》是該系列史上首款以多人遊戲為核心的一作,不過育碧也提供了一個單人模式,但是千萬別指望能夠獲得像《維加斯》那樣優秀的劇情體驗。
  • oc門電路工作原理分析
    打開APP oc門電路工作原理分析 發表於 2017-11-09 15:55:15   什麼是OC門
  • [深入淺出Cocoa]詳解鍵值觀察(KVO)及其實現機理
    羅朝輝 (http://www.cppblog.com/kesalin/)一,前言Objective-C 中的鍵(key)-值(value)觀察(KVO)並不是什麼新鮮事物,它來源於設計模式中的觀察者模式
  • 如何應用觀察者設計模式重構系統中日誌處理功能實現的程序代碼
    軟體項目實訓及課程設計指導——如何應用觀察者設計模式重構系統中的日誌處理功能實現的程序代碼1、GOF設計模式中的觀察者設計模式(1)什麼是觀察者設計模式GOF設計模式中的觀察者設計模式定義了一種解耦「一對多」的依賴關係的編程模式