IOS OS X 中集中消息的傳遞機制

1 KVO (key-value Observing)html

  是提供對象屬性被改變是的通知機制。KVO的實現實在Foundation中,不少基於 Foundation 的框架都依賴與它。若是隻對某一個對象的值的改變感興趣的話。就可使用KVO消息傳遞。知足KVO的前提條件:1接受者(接受對象改變的通知的對象)須要知道發送者(值會改變的對象);2,接受者須要知道發送者的生命週期,由於它須要在發送者被銷燬前註銷觀察者身份。若是這兩個要求都符合的話,這個消息傳遞機制能夠一對多(多個觀察者能夠註冊同一個對象的變化)ios

若是要在Core Data 上使用KVO的話,方法會有些許差異。這和Core Data的惰性加載(faulting) 機制有關,一旦一個managed object 被惰性加載處理的話,即便它的屬性沒有被改變,它仍是會出發相應的觀察者。緩存

注:把屬性值先取入緩存中,在對象須要的時候再進行一次訪問,這在 Core Data 中是默認行爲,這種技術稱爲 Faulting。這麼作能夠避免下降內存開銷,可是若是你肯定將訪問結果對象的具體屬性值時,能夠禁用 Faults 以提升獲取性能。app

2 通知 (Notification)框架

  要在代碼中的兩個不相關的模塊中傳遞消息時,通知機制是很是好的工具。通知機制廣播消息,當消息內容豐富並且無需期望接受者必定要關注的話這一招特別有用異步

通知能夠用來發送任意消息,甚至能夠包含一個userInfo字典。你能夠繼承NSNotification 寫一個本身的通知類類自定義行爲,通知的獨特之處在於,發送者和接受者不須要相互知道對方,因此通知能夠被用來在不一樣的相隔很遠的模塊之間傳遞消息。這就意味着這種消息的傳遞是單向的,咱們不能回覆通知/工具

3委託(Delegation)性能

  Delegation 在平果的框架中普遍存在,它讓咱們能自定義對象的行爲,並收到一些觸發的事件,要使用Delegation模式的話,發送者須要知道接受者,可是反過來沒有要求,應爲發送者只須要知道接受者符合必定的協議,因此他們二者結合的很鬆。ui

應爲delegation 協議能夠定義任何的方法,咱們能夠照着本身的需求來傳遞消息。能夠用方法參數來傳遞消息的內容,delegation 能夠經過方悔之的形式來給發送者作出迴應。若是隻要在相對接近的兩個模塊間傳遞消息 delegation 是很靈活很直接的消息傳遞機制。編碼

過分使用 delgation 也會帶來風險。若是戀歌對相結合的很緊密,任何其中一個對象都不能單獨運轉,那麼就不須要用delegate協議了 這些狀況下,對象已經知道各自的類型 能夠直接交流

塊 (Block)

Block 是最近才加入 Objective-C 的,首次出如今 OS X 10.6 和 iOS 4 平臺上。Block 一般能夠徹底替代 delegation 消息傳遞機制的角色。不過這兩種機制都有它們本身的獨特需求和優點。

一個不使用 block 的理由一般是 block 會存在致使 retain 環 (retain cycles) 的風險。若是發送者須要 retain block 但又不能確保引用在何時被賦值爲 nil, 那麼全部在 block 內對 self 的引用就會發生潛在的 retain 環。

假設咱們要實現一個用 block 回調而不是 delegate 機制的 table view 裏的選擇方法,以下所示:

self.myTableView.selectionHandler = ^void(NSIndexPath *selectedIndexPath) { 
// 處理選擇 
};

這兒的問題是,self 會 retain table view,table view 爲了讓 block 以後可使用而又須要 retain 這個 block。然而 table view 不能把這個引用設爲 nil,由於它不知道何時不須要這個 block 了。若是咱們不能保證打破 retain 環而且咱們須要 retain 發送者,那麼 block 就不是一個的好選擇。

NSOperation 是使用 block 的一個好範例。由於它在必定的地方打破了 retain 環,解決了上述的問題。

self.queue = [[NSOperationQueue alloc] init]; 
MyOperation *operation = [[MyOperation alloc] init]; 
operation.completionBlock = ^{ [self finishedOperation]; }; 
[self.queue addOperation:operation];

一眼看來好像上面的代碼有一個 retain 環:self retain 了 queue,queue retain 了 operation, operation retain 了 completionBlock, 而 completionBlock retain 了 self。然而,把 operation 加入 queue 中會使 operation 在某個時間被執行,而後被從 queue 中移除。(若是沒被執行,問題就大了。)一旦 queue 把 operation 移除,retain 環就被打破了。

另外一個例子是:咱們在寫一個視頻編碼器的類,在類裏面咱們會調用一個 encodeWithCompletionHandler: 的方法。爲了避免出問題,咱們須要保證編碼器對象在某個時間點會釋放對 block 的引用。其代碼以下所示:

@interface Encoder ()
@property (nonatomic, copy) void (^completionHandler)();
@end

@implementation Encoder

- (void)encodeWithCompletionHandler:(void (^)())handler
{
    self.completionHandler = handler;
    // 進行異步處理...
}

// 這個方法會在完成後被調用一次
- (void)finishedEncoding
{
    self.completionHandler();
    self.completionHandler = nil; // <- 不要忘了這個!
}

@end

一旦任務完成,completion block 調用過了之後,咱們就應該把它設爲 nil

若是一個被調用的方法須要發送一個一次性的消息做爲回覆,那麼使用 block 是很好的選擇, 由於這樣作咱們能夠打破潛在的 retain 環。另外,若是將處理的消息和對消息的調用放在一塊兒能夠加強可讀性的話,咱們也很難拒絕使用 block 來進行處理。在用例之中,使用 block 來作完成的回調,錯誤的回調,或者相似的事情,是很常見的狀況。

Target-Action

Target-Action 是迴應 UI 事件時典型的消息傳遞方式。iOS 上的 UIControl 和 Mac 上的 NSControl/NSCell 都支持這個機制。Target-Action 在消息的發送者和接收者之間創建了一個鬆散的關係。消息的接收者不知道發送者,甚至消息的發送者也不知道消息的接收者會是什麼。若是 target 是 nil,action 會在響應鏈 (responder chain) 中被傳遞下去,直到找到一個響應它的對象。在 iOS 中,每一個控件甚至能夠和多個 target-action 關聯。

基於 target-action 傳遞機制的一個侷限是,發送的消息不能攜帶自定義的信息。在 Mac 平臺上 action 方法的第一個參數永遠接收者。iOS 中,能夠選擇性的把發送者和觸發 action 的事件做爲參數。除此以外就沒有別的控制 action 消息內容的方法了。

作出正確的選擇

基於上述對不一樣消息傳遞機制的特色,咱們畫了一個流程圖來幫助咱們在不一樣情境下作出不一樣的選擇。一句忠告:流程圖的建議不表明最終答案。有些時候別的選擇依然能達到應有的效果。只不過大多數狀況下這張圖能引導你作出正確的決定。

communication-patterns-flow-chart

 

圖中有些細節值得深究:

有個框中說到: 發送者支持 KVO。這不只僅是說發送者會在值改變的時候發送 KVO 通知,並且說明觀察者須要知道發送者的生命週期。若是發送者被存在一個 weak 屬性中,那麼發送者有可能會本身變成 nil,那時觀察者會致使內存泄露。

一個在最後一行的框裏說,消息直接響應方法調用。也就是說方法調用的接收者須要給調用者一個消息做爲方法調用的直接反饋。這也就是說處理消息的代碼和調用方法的代碼必須在同一個地方。

最後在右下角的地方,一個選擇分支這樣說:發送者能確保釋放對 block 的引用嗎?這涉及到了咱們以前討論 block 的 API 存在潛在的 retain 環的問題。若是發送者不能保證在某個時間點會釋放對 block 的引用,那麼你會惹上 retain 環的麻煩。

相關文章
相關標籤/搜索