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