標籤:http 使用 strong os 2014 art
1 KVO (key-value Observing)
是提供對象屬性被改變是的通知機制。KVO的實現實在Foundation中,很多基於 Foundation 的架構都依賴與它。如果只對某一個對象的值的改變感興趣的話。就可以使用KVO訊息傳遞。滿足KVO的前提條件:1接受者(接受對象改變的通知的對象)需要知道寄件者(值會改變的對象);2,接受者需要知道寄件者的生命週期,因為它需要在寄件者被銷毀前登出觀察者身份。如果這兩個要求都符合的話,這個訊息傳遞機制可以一對多(多個觀察者可以註冊同一個對象的變化)
如果要在Core Data 上使用KVO的話,方法會有些許差別。這和Core Data的惰性載入(faulting) 機制有關,一旦一個managed object 被惰性載入處理的話,即使它的屬性沒有被改變,它還是會出發相應的觀察者。
註:把屬性值先取入緩衝中,在對象需要的時候再進行一次訪問,這在 Core Data 中是預設行為,這種技術稱為 Faulting。這麼做可以避免降低記憶體開銷,但是如果你確定將訪問結果對象的具體屬性值時,可以禁用 Faults 以提高擷取效能。
2 通知 (Notification)
要在代碼中的兩個不相關的模組中傳遞訊息時,通知機制是非常好的工具。通知機制廣播訊息,當訊息內容豐富而且無需指望接受者一定要關注的話這一招特別有用
通知可以用來發送任意訊息,甚至可以包含一個userInfo字典。你可以繼承NSNotification 寫一個自己的通知類類自訂行為,通知的獨特之處在於,寄件者和接受者不需要相互知道對方,所以通知可以被用來在不同的相隔很遠的模組之間傳遞訊息。這就意味著這種訊息的傳遞是單向的,我們不能回複通知/
3委託(Delegation)
Delegation 在平果的架構中廣泛存在,它讓我們能自訂對象的行為,並收到一些觸發的事件,要使用Delegation模式的話,寄件者需要知道接受者,但是反過來沒有要求,應為寄件者只需要知道接受者符合一定的協議,所以他們兩者結合的很松。
應為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 訊息內容的方法了。
做出正確的選擇
基於上述對不同訊息傳遞機制的特點,我們畫了一個流程圖來協助我們在不同情境下做出不同的選擇。一句忠告:流程圖的建議不代表最終答案。有些時候別的選擇依然能達到應有的效果。只不過大多數情況下這張圖能引導你做出正確的決定。
圖中有些細節值得深究:
有個框中說到: 寄件者支援 KVO。這不僅僅是說寄件者會在值改變的時候發送 KVO 通知,而且說明觀察者需要知道寄件者的生命週期。如果寄件者被存在一個 weak 屬性中,那麼寄件者有可能會自己變成 nil,那時觀察者會導致記憶體泄露。
一個在最後一行的框裡說,訊息直接回應程式法調用。也就是說方法調用的接收者需要給調用者一個訊息作為方法調用的直接反饋。這也就是說處理訊息的代碼和調用方法的代碼必須在同一個地方。
最後在右下角的地方,一個選擇分支這樣說:寄件者能確保釋放對 block 的引用嗎?這涉及到了我們之前討論 block 的 API 存在潛在的 retain 環的問題。如果寄件者不能保證在某個時間點會釋放對 block 的引用,那麼你會惹上 retain 環的麻煩。