在每一個應用程序中都是包含著許多松散耦合的對象,它們之間采用何種通信模式進行交互?哪一種通信機制是最佳的實踐?這些都是值得思考的問題(誠然其中沒有明確的答案,但是也有一些約定俗成的用法習慣)。
通常情況下,我們可以采用的通信模式包括:KVO,Notifications,delegation,blocks,target-action。
一、了解Communication Patterns
1、KVO
KVO機制用於通知對象屬性的變化。
消息接收者-->接收對象屬性變化的消息;消息發送者-->對象屬性發生變化。
消息接收者通過注冊對消息發送者的監聽,還必須知道消息發送者的生命期,用以解除對其的注冊。
2、Notifications
Notifications(消息通知)用於在代碼間廣播消息。
Notifications發送的消息是任意的,你可以通過 userInfo 字典或者用 NSNotification 消息載體裝載發送的消息。
通過Notifications,消息發送者和接收者可以互不認識,即在松耦合的代碼間傳遞消息。此外,這是一種單向的通信方式,即接收方不能回復Notifications的消息通知。
3、Delegation(委托)
委托的作用:傳值;傳事件。
在委托協議中定義任意需要的委托方法處理兩個特定對象之間的通信。發送者即委托者,在委托協議中定義委托方法,接收者即被委托者,實現委托協議中的委托方法。
4、Blocks
Blocks通常用於回調事件處理等等,是一種相對較新的技術。一般情況下,block可以滿足用delegation實現的消息傳遞機制,不過這兩種機制還是有各自的特色。
潛在的風險:retain cycle。由於被blocks引用的變量都會被自動 retain 一次,如果不能保證被引用的變量置nil,那麼使用Blocks就會出現 retain cycles。
例如:
DoSomethingManager *manager = [[DoSomethingManager alloc] init]; manager.complete = ^{ //...complete actions [manager otherAction]; [manager release]; };在這個例子中,由於manager和block相互持有,那麼即使調用了release,還是形成了retain cycle。
為了解除retain cycle,那麼代碼修改為:
DoSomethingManager *manager = [[DoSomethingManager alloc] init]; manager.complete = ^{ //...complete actions [manager otherAction]; manager.complete = nil; [manager release]; };
再看一個NSOperation的例子:
self.queue = [[NSOperationQueue alloc] init]; MyOperation *operation = [[MyOperation alloc] init]; operation.completionBlock = ^{ [self finishedOperation]; }; [self.queue addOperation:operation];
但是,一個operation添加到queue後,必定會在某一個時刻執行結束,然後退出queue,那麼一旦operation退出queue後,retain cycle也就被打破了。
5、Target-Action
Target-Action是用來響應UI界面事件發送消息的典型模式。
及時消息響應的target為nil空,action也會沿著 responder chain(響應鏈)找到一個對象進行響應。
Target-Action的限制是消息發送不能攜帶任何自定義的負載消息。
二、Making the Right Choice
基於上述通信模式的特點,構建了如下的流程圖,有助於在選擇哪種模式做出正確的決定。(這張圖的建議並不一定是最終的答案)
三、Framework Examples
1、KVO
NSOperationQueue 使用 KVO 機制觀察 operation 的狀態屬性變化(isFinished,isExecuting,isCancelled),當operation的狀態屬性變化時,NSOperationQueue 就會接收到一個 KVO 消息通知。
NSOperationQueue -- 消息的接收者
operation -- 消息的發送者
顯然,二者需要一個單向的通信機制,而且NSOperationQueue 只對 operation對象的狀態屬性的變化感興趣,所以采用KVO是最佳的實踐。
誠然,KVO是非常不錯的實踐方式。當然似乎也可以采用Delegation實現。
operation queue 是 operation 的委托(delegation),operation 可以調用諸如 operationDidFinish 或者 operationDidBeginExecuting 等委托協議方法通知 operation queue 有關 operation 狀態屬性的變化。
2、Notifications
Core Data 使用 notification 在事件間通信,例如:managed object context(NSManagedObjectContextDidChangeNotification)
managed object context 發送的變化通知,其消息接收方可以不必知曉誰是發送者,而且這不是一個UI事件,那麼其接收者可以多個。說明這裡需要一個單向的通信管道,那麼 Notifications 就是唯一的選擇了。
3、Delegation
Table View 的實現就采用了 Delegation 。例如tableView:didSelectRowAtIndexPath: 方法。為什麼要以delegate調用的方式來實現?而又為啥不用target-action方式?
正如我們在流程圖中看到的一樣,使用target-action時,不能傳遞自定義的數據。而在選中table view的某個cell時,collection view不僅僅需要告訴我們有一個cell被選中了,還需要告訴我們是哪個cell被選中了(index path)。按照這樣的一種思路,那麼從流程圖中可以看到應該使用delegation機制。
4、Blocks
關於block的介紹,我們來看看[NSURLSession dataTaskWithURL:completionHandler:]吧。從URL loading system返回到調用者,這個過程具體是如何傳遞消息的呢?首先,作為這個API的調用者,我們知道消息的發送者,但是我們並沒有retain這個發送者。另外,這屬於單向消息傳遞——直接調用dataTaskWithURL:方法。如果按照這樣的思路對照著流程圖,我們會發現應該使用基於block消息傳遞的機制。
還有其它可選的機制嗎?當然有了,蘋果自己的NSURLConnection就是最好的例子。NSURLConnection在block問世之前就已經存在了,所以它並沒有利用block進行消息傳遞,而是使用delegation機制。當block出現之後,蘋果在NSURLConnection中添加了sendAsynchronousRequest:queue:completionHandler:方法(OSX 10.7 iOS 5),因此如果是簡單的task,就不必在使用delegate了。
在OS X 10.9 和 iOS 7中,蘋果引入了一個非常modern的API:NSURLSession,其中使用block當做消息傳遞機制(NSURLSession仍然有一個delegate,不過是用於別的目的)。
5、Target-Action
Target-Action用的最明顯的一個地方就是button(按鈕)。button除了需要發送一個click事件以外,並不需要再發送別的信息了。所以Target-Action在用戶界面事件傳遞過程中,是最佳的選擇。
如果taget已經明確指定了,那麼action消息回直接發送給指定的對象。如果taget是nil,action消息會以冒泡的方式在響應鏈中查找一個能夠處理該消息的對象。此時,我們擁有一種完全解耦的消息傳遞機制——發送者不需要知道接收者,以及其它一些信息。
Target-Action非常適用於用戶界面中的事件。目前也沒有其它合適的消息傳遞機制能夠提供同樣的功能。雖然notification最接近這種在發送者和接收者解耦關系,但是target-action可以用於響應鏈(responder chain)——只有一個對象獲得action並作出響應,並且action可以在響應鏈中傳遞,直到遇到能夠響應該action的對象。
四、小結
對於兩個對象之間的消息傳遞使用何種通信模式有時的確有些模稜兩可,但是仔細考慮琢磨,會發現其各自有的需求和功能特性。
文章中給出的決策流程圖只是選擇時候提供的參考依據,並非最終的答案,實踐出真知!
注:本篇文章源自 objc.io 文章 Communication Patterns ,其全文翻譯:iOS中消息的傳遞機制