設計模式-觀察者模式KVO

1.什么是觀察者模式?
2.為什么要用觀察者模式?它的優缺點是什么?
![Uploading 屏幕快照 2016-12-20 下午3.23.56_660666.png . . .]
3.觀察者模式解決了什么問題?你的項目中哪里用到了觀察者模式?
4.觀察者模式怎么用?有幾種方式?
5.怎么創建觀察者模式?
7.寫觀察者模式的時候需要注意什么問題?
8.iOS源代碼中哪里用到了觀察者模式?舉例說明。
9.實現原理,

根據上面的套路,我們一一回答問題。
1.什么是觀察者模式

KVO 是 Objective-C 對觀察者設計模式的一種實現。【另外一種是:通知機制(notification)
KVO提供一種機制,指定一個被觀察對象(例如A類),當對象某個屬性(例如A中的字符串name)發生更改時,對象會獲得通知,并作出相應處理;【且不需要給被觀察的對象添加任何額外代碼,就能使用KVO機制】
在MVC設計架構下的項目,KVO機制很適合實現mode模型和view視圖之間的通訊。
例如:代碼中,在模型類A創建屬性數據,在控制器中創建觀察者,一旦屬性數據發生改變就收到觀察者收到通知,通過KVO再在控制器使用回調方法處理實現視圖B的更新;(本文中的應用就是這樣的例子.)

簡單的說就是,當某對象改變時,自動通知所有相關的狀態進行更新。

2.為什么要用觀察者模式
觀察者模式定義了一種一對多的依賴關系,讓多個觀察者對象同時監聽某一個主題對象。這個主題對象在狀態上發生變化時,會通知所有觀察者對象,使它們能夠自動更新自己。

觀察者模式的優點:
1、 Subject和Observer之間是松偶合的,分別可以各自獨立改變。
2、Subject在發送廣播通知的時候,無須指定具體的Observer,Observer可以自己決定是否要訂閱Subject的通知。
3、高內聚、低偶合。

3.觀察者模式解決了什么問題?你的項目中哪里用到了觀察者模式?
有時候我們需要監聽某個類的屬性值的變化從而做出相應的改變,這個時候使用KVO/KVC設計模式。
比如,在項目中,我需要監聽model中的某個屬性值的變換,當變化時,需要更新UI顯示。

//增加frame的監聽 
1)用于判斷當前手勢滑動的frame moveTableView:didChangeFromFrame:toFrame
2)增加搜索結果contentview 根據手勢切換導航顯示模式
    [self addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];

4.觀察者模式怎么用?有幾種方式?
在iOS中觀察者模式的實現有四種方法:NSNotification、KVO、Protocol以及Code Block代碼塊。

要點:
Notification是一對多的,而delegate回調是一對一的。

Notification - NotificationCenter機制使用了操作系統的對象間通訊功能,而delegate是直接的函數調用。Notification跨度大,而delegate效率可能比較高。

相較于前兩者KVO才是一種真正的觀察者模式,它允許你將一個處理函數綁定到某個類的屬性,屬性發生改變是就會自動觸發,不像其他兩種需要你手動的發通知。KVO是一種非常靈活的觀察機制,廣泛應用于界面設計。

Code Block其實就相當于C的函數指針,可以用來做各種回調。我覺得其應當具備最高的效率。使用Code Block要注意的地方就是使用外部變量。在block里直接引用外部變量的話會在block定義的時候復制外部變量的一個拷貝,也就是說得到的是block定義時的值,在block內修改這個值也不會傳給外部。要得到實時的數據,或者將數據傳出的話需要在相關變量前面加__block即可。

NSNotification

屏幕快照 2016-12-20 下午3.22.55.png

發送通知:

屏幕快照 2016-12-20 下午3.25.41.png

實現通知的方法


屏幕快照 2016-12-20 下午3.23.56.png

KVO

//注冊通知
 [_tableView addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL];
//通知回調
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    if ([keyPath isEqualToString:@"frame"]) {
        //處理邏輯
    }
}
//移除通知
- (void)dealloc
{
    [_tableView removeObserver:self forKeyPath:@"frame"];
}

protocol/delegate

需要注意的問題:
1)A對象要通知B對象,B對象必須實現監聽的方法,否則一旦有消息發送就會導致崩潰.

  1. A對象不想通知B對象了,需要從B對象身上移除掉通知.

但是要注意:添加的觀察者的次數要和移除觀察者的次數相等,少移除一個或者多移除一個都會造成程序崩潰:
3)嚴重依賴于string
KVO嚴重依賴string,換句話說,KVO中的keyPath必須是NSString這個事實使得編譯器沒辦法在編譯階段將錯誤的keyPath給找出來;譬如很容易將「contentSize」寫成「content size」;
4)

KVO的實現
KVO的實現也依賴于Objective-C的Runtime。
簡單概述下KVO的實現:
當你觀察一個對象(稱該對象為「被觀察對象」)時,一個新的類會動態被創建。這個類繼承自「被觀察對象」所對應類的,并重寫該被觀察屬性的setter方法;針對setter方法的重寫無非是在賦值語句前后加上相應的通知;最后,把「被觀察對象」的isa指針(isa指針告訴Runtime系統這個對象的類是什么)指向這個新創建的中間類,對象就神奇變成了新創建類的實例。
根據文檔的描述,雖然被觀察對象的isa指針被修改了,但是調用其class方法得到的類信息仍然是它之前所繼承類的類信息,而不是這個新創建類的類信息。
補充:下面對isa指針和類方法class作以更多的說明。
isa指針和類方法class的返回值都是Class類型,如下:
@interfaceNSObject {
ClassisaOBJC_ISA_AVAILABILITY;
}

  • (Class)class;
    根據我的理解,一般情況下,isa指針和class方法返回值都是一樣的;但KVO底層實現時,動態創建的類只是重寫了被觀察屬性的setter方法,并未重寫類方法class,因此向被觀察者發送class消息實際上仍然調用的是被觀察者原先類的類方法+ (Class)class,得到的類型信息當然是原先類的類信息,根據我的猜測,isKindOfClass:和isMemberOfClass:與class方法緊密相關。
    國外的大神Mike Ash早在2009年就做了關于KVO的實現細節的探究,更多詳細參考這里

下面來對這兩個參數進行詳細介紹。
options
options可選值是一個NSKeyValueObservingOptions枚舉值,到目前為止,一共包括四個值,在介紹這四個值各自表示的意思之前,先得有一個概念,即KVO響應方法有一個NSDictionary類型參數change(下面『響應』中可以看到),這個字典中會有一個與被監聽屬性相關的值,譬如被改變之前的值、新值等,NSDictionary中有啥值由『訂閱』時的options值決定,options可取值如下:
NSKeyValueObservingOptionNew: 指示change字典中包含新屬性值;
NSKeyValueObservingOptionOld: 指示change字典中包含舊屬性值;
NSKeyValueObservingOptionInitial: 相對復雜一些,NSKeyValueObserving.h文件中有詳細說明,此處略過;
NSKeyValueObservingOptionPrior: 相對復雜一些,NSKeyValueObserving.h文件中有詳細說明,此處略過;
現在細想,options這個參數也忒復雜了,難怪大神們覺得這個API丑陋(不過我等小民之前從未想過這個問題,=_=,沒辦法,Apple是個大帝國,我只是其中一個跪舔的小屁民)。
不過更糟心的是下面的context參數。

context
options信息量稍大,但其實蠻好理解的,然而對于context,在寫這篇博客之前,一直不知道context參數有啥用(也沒在意)。
context作用大了去了,在上文『KVO的槽點』提到一個槽點『多次相同的removeObserver會導致crash』。導致『多次調用相同的removeObserver』一個很重要的原因是我們經常在addObserver時為context參數賦值NULL,關于如何使用context參數,下面的『響應』中會提到。

響應
iOS的UI交互(譬如UIButton的一次點擊)有一個非常不錯的消息轉發機制 — Target-Action模型,簡單來說,為指定的event指定target和action處理方法。
UIButtonbutton = [UIButtonnew];
[button addTarget:selfaction:@selector(buttonDidClicked:) forControlEvents:UIControlEventTouchUpInside];
這種target-action模型邏輯非常清晰。作為對比,KVO的響應處理就非常糟糕了,所有的響應都對應是同一個方法- (void)observeValueForKeyPath:ofObject:change:context:,其原型如下:
-(void)observeValueForKeyPath:(NSString
)keyPath
ofObject:(id)object
change:(NSDictionary*)change
context:(void *)context;
除了NSDictionary類型參數change之外,其余幾個參數都能在–addObserver:forKeyPath:options:context:找到對應。

下面將針對「嚴重依賴于string」和「多次相同的removeObserver會導致crash」這兩個槽點對keyPath和context參數進行闡述。
keyPath
keyPath的類型是NSString,這導致了我們使用了錯誤的keyPath而不自知,譬如將@”contentSize”錯誤寫成@”contentsize”,一個更好的方法是不直接使用@”xxxoo”,而是積極使用NSStringFromSelector(SEL aSelector)方法,即改@"contentSize"為NSStringFromSelector(@selector(contentSize))。

context
對于context,上文已經提到一種場景:假如父類(設為ClassA)和子類(設為ClassB)都監聽了同一個對象腫么辦?是ClassB處理呢還是交給父類ClassA的observeValueForKeyPath:ofObject:change:context:處理呢?更復雜一點,如果子類的子類(設為ClassC)也監聽了同一個對象,當ClassB接收到ClassC的[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];消息時又該如何處理呢?

用context參數判斷!

在addObserver時為context參數設置一個獨一無二的值即可,在responding處理時對這個context值進行檢驗。如此就解決了問題,但這需要靠用戶(各個層級類的程序員用戶)自覺遵守。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,983評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,772評論 3 422
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,947評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,201評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,960評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,350評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,406評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,549評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,104評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,914評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,089評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,647評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,340評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,753評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,007評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,834評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,106評論 2 375

推薦閱讀更多精彩內容