上周我們跟隨南昌APP開(kāi)發(fā)制作公司--百恒網(wǎng)絡(luò)一起學(xué)習(xí)了ios設(shè)計(jì)模式之常用模式和委托模式,今天我們繼續(xù)來(lái)學(xué)習(xí)ios設(shè)計(jì)模式之觀(guān)察者模式。
觀(guān)察者(Observer)模式也叫發(fā)布/訂閱(Publish/Subscribe)模式,是 MVC( 模型-視圖-控制器)模式的重要組成部分。
3.1 問(wèn)題提出
天氣一直是英國(guó)人喜歡討論的話(huà)題,而最近幾年天氣也成為中國(guó)人非常關(guān)注的話(huà)題。我會(huì)根據(jù)天氣預(yù)報(bào)決定是坐地鐵還是開(kāi)車(chē)上班,也會(huì)根據(jù)天氣預(yù)報(bào)決定明天穿哪件衣服。于是在移動(dòng)公司為我的手機(jī)定制了天氣預(yù)報(bào)短信通知服務(wù),它的工作模型如圖所示。
定制天氣預(yù)報(bào)短信通知服務(wù)
每天氣象局將天氣預(yù)報(bào)信息投送給移動(dòng)運(yùn)營(yíng)商,移動(dòng)運(yùn)營(yíng)商的短信中心負(fù)責(zé)把天氣預(yù)報(bào)發(fā)送給定制過(guò)這項(xiàng)服務(wù)的手機(jī)。
在軟件系統(tǒng)中,一個(gè)對(duì)象狀態(tài)的改變也會(huì)連帶影響其他很多對(duì)象的狀態(tài)發(fā)生改變。能夠?qū)崿F(xiàn)這一需求的設(shè)計(jì)方案有很多,但能夠做到復(fù)用性強(qiáng)且對(duì)象之間匿名通信的,觀(guān)察者模式是其中最為適合的一個(gè)。
3.2 實(shí)現(xiàn)原理
觀(guān)察者模式的類(lèi)圖如圖所示。 它有4個(gè)角色,具體如下所示。
抽象主題( Subject )。抽象主題是一個(gè)協(xié)議,它是一個(gè)觀(guān)察者集合容器,定義了添加觀(guān)察者( attach )方法、移除觀(guān)察者( detach )方法和為所有觀(guān)察者發(fā)送通知的方法( notifyObserver )。
抽象觀(guān)察者( Observer )。抽象觀(guān)察者也是一個(gè)協(xié)議,它有一個(gè)更新( update )方法。
具體觀(guān)察者( ConcreteObserver )。 Observer 協(xié)議的具體實(shí)現(xiàn)。
具體主題( ConcreteSubject )。 Subject 協(xié)議的具體實(shí)現(xiàn)。
引入 Subject 和 Observer 這兩個(gè)協(xié)議后,不僅提高了系統(tǒng)的可復(fù)用性,還降低了耦合度。觀(guān)察者模式還可以有其他變形,若要深入了解,可以參考GoF。
觀(guān)察者模式的類(lèi)圖(上圖為Swift版,下圖為Objective-C版)
3.3 通知機(jī)制和 KVO 機(jī)制
在Cocoa Touch框架中,觀(guān)察者模式的具體應(yīng)用有兩個(gè)——通知(notification)機(jī)制和KVO(Key-ValueObserving)機(jī)制,下面簡(jiǎn)要介紹這兩種機(jī)制。
1. 通知機(jī)制
通知機(jī)制與委托機(jī)制不同的是,前者是“一對(duì)多”的對(duì)象之間的通信,后者是“一對(duì)一”的對(duì)象之間的通信。
說(shuō)明:在iOS中,通知一詞多次出現(xiàn)過(guò),歸納一下主要有廣播通知(broadcast notification)、本地通知(localnotification)和推送通知(push notification),本節(jié)介紹的是廣播通知。事實(shí)上,除了名字相似,廣播通知與其他兩個(gè)通知完全不同:廣播通知是Cocoa Touch框架中實(shí)現(xiàn)觀(guān)察者模式的一種機(jī)制,它可以在一個(gè)應(yīng)用內(nèi)部的多個(gè)對(duì)象之間發(fā)送消息;本地通知和推送通知中的“通知”是給用戶(hù)一種“提示”,它的“提示”方式有警告對(duì)話(huà)框、發(fā)出聲音、振動(dòng)和在應(yīng)用圖標(biāo)上顯示數(shù)字等。 在計(jì)劃時(shí)間達(dá)到時(shí),本地通知由本地iOS發(fā)出。推送通知由第三方程序發(fā)送給蘋(píng)果的遠(yuǎn)程服務(wù)器,再由遠(yuǎn)程服務(wù)器推送給iOS的特定應(yīng)用。
如圖所示,在通知機(jī)制中對(duì)某個(gè)通知感興趣的所有對(duì)象都可以成為接收者。首先,這些對(duì)象需要向通知中心( NSNotificationCenter )發(fā)出addObserver:selector:name:object: 消息進(jìn)行注冊(cè),在投送對(duì)象投送通知給通知中心時(shí),通知中心就會(huì)把通知廣播給注冊(cè)過(guò)的接收者。所有的接收者都不知道通知是誰(shuí)投送的,更不關(guān)心它的細(xì)節(jié)。
投送對(duì)象與接收者是一對(duì)多的關(guān)系。接收者如果對(duì)通知不再關(guān)注,會(huì)給通知中心發(fā)出 removeObserver:name:object:消息解除注冊(cè),以后不再接收通知。
通知機(jī)制圖
下面我們介紹一下通知機(jī)制的使用過(guò)程。這里我們將7.1節(jié)的模態(tài)視圖案例重新設(shè)計(jì)一下,圖8-15所示,在注冊(cè)視圖點(diǎn)擊Save按鈕返回到登錄視圖時(shí),把數(shù)據(jù)回傳給登錄視圖。
模態(tài)視圖案例
登錄視圖控制器 ViewController 作為通知的接收者,注冊(cè)視圖控制器 RegisterViewController 作為通知投送對(duì)象,如圖所示。
通知機(jī)制圖
在ViewController視圖控制器中,注冊(cè)通知接收者的代碼如下:
NSNotificationCenter 是單例模式,創(chuàng)建和獲得共享實(shí)例的方法是 defaultCenter , NSNotificationCenter 的addObserver:selector:name:object:方法能夠 注冊(cè)通知。當(dāng)接收到 AppWillTerminateNotification 通知時(shí),就會(huì)調(diào)用handleTerminate: 方法。
解除注冊(cè)代碼也類(lèi)似,通過(guò) NSNotificationCenter 發(fā)出 removeObserver 消息實(shí)現(xiàn)。對(duì)于視圖控制器,也可以在didReceiveMemoryWarning 方法中發(fā)出消息,具體代碼如下:
ViewController中接收通知的方法是 registerCompletion: ,其代碼如下:
這個(gè)方法可以接收一個(gè) NSNotification類(lèi)型 的參數(shù)。NSNotification 類(lèi)中有3個(gè)重要的屬性: name 、 object 和userInfo ,這3個(gè)屬性與通知中心投送方法中的參數(shù)有一定的對(duì)應(yīng)關(guān)系,如圖所示。
NSNotification 類(lèi)和通知中心中投送方法參數(shù)的關(guān)系
其中 name 是通知的名字, object 是投送通知時(shí)傳遞過(guò)來(lái)的對(duì)象, userInfo 是投送通知時(shí)定義的字典對(duì)象,可借助于該參數(shù)傳遞數(shù)據(jù)。
在 RegisterViewController 視圖控制器中,投送通知的代碼如下:
NSNotificationCenter 的投送方法除了代碼中所示外,還有另外兩個(gè)重載方法:
它們可以投送不帶 userInfo 參數(shù)的通知,我們可以根據(jù)需要進(jìn)行選擇。還要注意的是, object 參數(shù)未必是 self對(duì)象,我們可以根據(jù)需要傳遞一個(gè)對(duì)象,如果接收者不需要,可以將其設(shè)為 nil 。
當(dāng)我們運(yùn)行代碼,從注冊(cè)視圖進(jìn)入登錄視圖后,會(huì)在日志中輸出回傳回來(lái)的 username 參數(shù)。
Cocoa和Cocoa Touch框架都提供一些通知,由系統(tǒng)自動(dòng)投送?,F(xiàn)在修改 ViewController 類(lèi)添加系統(tǒng)通知:
在 viewDidLoad 方法中,第①~②行代碼是系統(tǒng)注冊(cè)通知 UIApplicationDidEnterBackgroundNotification (進(jìn)入到后臺(tái)通知)和:
UIApplicationWillEnterForegroundNotification (回到前臺(tái)通知)。第③~④行代碼是接收通知后的事件處理。
提示 在iOS設(shè)備上可以按Home鍵進(jìn)入后臺(tái),快速按兩次Home鍵可以打開(kāi)最近使用應(yīng)用列表,點(diǎn)擊應(yīng)用可以使該應(yīng)用重新回到前臺(tái)。另外,在Xcode模擬器中沒(méi)有Home鍵,點(diǎn)擊Home鍵的操作可以通過(guò)組合鍵command+shift+H完成。
除了應(yīng)用生命周期的不同階段有不同的通知外,很多控件也會(huì)在某些事件發(fā)生時(shí)投送通知,例如UITextField控件。在編輯過(guò)程的不同階段,UITextField控件會(huì)分別發(fā)出如下通知:UITextFieldTextDidBeginEditingNotification 、 UITextFieldTextDidChangeNotification 和 UITextFieldTextDidEndEditingNotification 。
2. KVO機(jī)制
KVO不像通知機(jī)制那樣通過(guò)一個(gè)通知中心通知所有觀(guān)察者對(duì)象,而是在對(duì)象屬性變化時(shí)通知會(huì)被直接發(fā)送給觀(guān)察者對(duì)象。圖為KVO機(jī)制解析圖。
可以看到,屬性發(fā)生變化的對(duì)象需要發(fā)出消息 addObserver:forKeyPath:options:context: 給注冊(cè)觀(guān)察者,使觀(guān)察者關(guān)注它的某個(gè)屬性的變化。當(dāng)對(duì)象屬性變化時(shí),觀(guān)察者就會(huì)接收到通知,觀(guān)察者需要重寫(xiě)方法 observeValueForKeyPath:ofObject:change:context: 以響應(yīng)屬性的變化。
KVO機(jī)制圖
下面我們來(lái)看一個(gè)實(shí)際的案例。我們使用KVO機(jī)制來(lái)監(jiān)視應(yīng)用程序的狀態(tài)變化。應(yīng)用程序委托對(duì)象
AppDelegate 的 appStatus 屬性是要觀(guān)察的屬性。 AppDelegate 的代碼如下:
上述代碼中第①行的 appStatus 屬性是需要觀(guān)察的屬性,在Swift版中它的定義上必須要加 dynamic,以表示該屬性是在運(yùn)行時(shí)動(dòng)態(tài)派發(fā)的。第②行代碼用于定義觀(guān)察者AppStatusObserver , AppStatusObserver 是我們的自定義類(lèi),它負(fù)責(zé)觀(guān)察 appStatus 屬性的變化。第③行代碼用于創(chuàng)建 AppStatusObserver 對(duì)象。
第④行代碼是關(guān)鍵,addObserver:forKeyPath:options:context: 語(yǔ)句告訴觀(guān)察者( AppStatusObserver )開(kāi)始觀(guān)察 AppDelegate 的 appStatus 屬性變化,其中參數(shù) addObserver 是觀(guān)察者對(duì)象; forKeyPath 是被關(guān)注對(duì)象的屬性;options 是為屬性變化設(shè)置的選項(xiàng),本例中 New 和 Old 表示把屬性新舊兩個(gè)值都傳遞給觀(guān)察者,這些值是NSKeyValueObservingOptions 類(lèi)型的成員; context 參數(shù)是上下文內(nèi)容,它的類(lèi)型是C語(yǔ)言形式的任何指針類(lèi)型,
Swift版表示為 UnsafeMutablePointer ,Objective-C版本表示為 void * 。
觀(guān)察者 AppStatusObserver 的代碼如下:
因?yàn)?NSObject 類(lèi)實(shí)現(xiàn)了 NSKeyValueObserving 協(xié)議,所以只需聲明 AppStatusObserver 繼承了 NSObject 類(lèi),而無(wú)需實(shí)現(xiàn) NSKeyValueObserving 協(xié)議。
observeValueForKeyPath:ofObject:change:context: 方法的 observeValueForKeyPath 參數(shù)是被關(guān)注的屬性。
ofObject 是被關(guān)注的對(duì)象, change 是字典類(lèi)型,包含了屬性變化的內(nèi)容,這些內(nèi)容與注冊(cè)時(shí)屬性變化設(shè)置的選項(xiàng)( options 參數(shù))有關(guān)。 context 是注冊(cè)時(shí)傳遞的上下文內(nèi)容。
第一次運(yùn)行程序到界面時(shí),會(huì)有兩個(gè)狀態(tài)的變化,日志結(jié)果如下:
如果將應(yīng)用退到后臺(tái),然后再回到前臺(tái),日志結(jié)果如下:
關(guān)于應(yīng)用程序狀態(tài)變化相關(guān)的內(nèi)容,這里不再解釋。
本文僅限內(nèi)部技術(shù)人員學(xué)習(xí)交流,不得作于其他商業(yè)用途.希望此文對(duì)廣大技人員有所幫助。文章出自:南昌APP開(kāi)發(fā)制作公司--百恒網(wǎng)絡(luò):http://www.gimmickmag.com