前文:網路上找了很多關於delegation和block的使用場景,發現沒有很滿意的解釋,後來無意中在stablekernel找到了這篇文章,文中作者不僅僅是給出瞭解決方案,更值得我們深思的是作者獨特的思考和解決問題的方式,因此將這篇文章翻譯過來,和諸君探討,翻譯的很多地方不是很到位,望大家提出意見建議。
有人問了我一個很棒的問題,我把這個問題總結為:“開發過程中該選擇 blocks or delegates?當我們需要實現回撥的時候,使用哪一種方式比較合適呢?”
一般在這種情況下,我喜歡問我自己:“如果問題交給Apple,他會怎麼做呢?”當然,我們都知道Apple肯定知道怎麼做,因為從某一層面上看,Apple的文件就是一本用來指導我們如何使用設計模式的指導書。
因此我們需要去研究一下Apple分別是在什麼情況下使用delegate和block,如果我們發現了Apple做這種選擇的套路,我們就可以構建出一些規則,可以幫助在我們在自己的程式碼中做相同選擇。
要找出Apple使用delegate的場景很簡單,我們只要搜尋官方文件中的“delegate”,就會獲取到很多使用delegation的類。
但是搜尋Apple中有關使用blocks的文件就有點困難了,因為我們不能直接搜尋文件中的“^” 。然而,Apple宣告方法時有很好的命名習慣(這也是我們精通iOS開發的一項必備技能)。例如:一個以NSString為引數的方法,方法的selector就會有String字眼,像initWithString;dateFromString;StartSpeaingString。
當Apple的方法使用block,這個方法將會有“Handler”,“Completion”或者簡單的“Block”作為selector;因此我們可以在標準的iOS API文件中搜尋這些關鍵詞,用以構建一個可信任的block用例列表。
1.大多數delegate protocols 都擁有幾個訊息源。
以我正在看的GKMatch為例(A GKMatch object provides a peer-to-peer network between a group of devices that are connected to Game Center,是iOS API中用來提供一組裝置連線到Game Center點對點網路的物件)。從這個類中可以看到訊息的來源分別是:當從其他玩家那接收到資料、當玩家切換了狀態、當發生錯誤或者當一個玩家應該被重新邀請。這些都是不同的事件。如果Apple在這裡使用block,那麼可能會有以下兩種解決方式:
- 可以對應每一個事件註冊相應的block,顯然這種方式是不合理的。( If someone writes a class that does this in Objective-C, they are probably an asshole.)
- 建立一個可以接受任何可能輸入的block
1 |
void (^matchBlock)(GKMatchEvent eventType, Player *player, NSData *data, NSError *err); |
因此,我們可以得出一個結論:如果物件有超過一個以上不同的事件源,使用delegation。
2.一個物件只能有一個delegate
由於一個物件只能有一個delegate,而且它只能與這個delegate通訊。讓我們看看CLLocationManager 這個類,當發現地理位置後,location manager 只會通知一個物件(有且只有一個)。當然,如果我們需要更多的物件去知道這個更新,我們最好建立其他的location manager。
這裡有的人可能想到,如果CLLocationManager是個單例呢?如果我們不能建立CLLocationManager的其他例項,就必須不斷地切換delegate指標到需要地理資料的物件上(或者建立一個只有你理解的精密的廣播系統)。因此,這樣看起來,delegatetion在單例上沒有多大意義。
關於這點,最好的印證例子就是UIAccelerometer。在早期版本的iOS中,單例的 accelerometer 例項有一個delegate,導致我們必須偶爾切換一下。這個愚蠢的問題在之後的IOS版本被修改了,現在,任意一個物件都可以訪問CMMotionManager block,而不需要阻止其他的物件來接收更新。
因此,我們可以得出另一個結論:“如果一個物件是單例,不要使用delegation”。
3.一般的delegate方法會有返回值
如果你觀察一些delegate方法(幾乎所有的dataSource方法)都有一個返回值。這就意味著delegating物件在請求某些東西的state(物件的值,或者物件本身),而一個block則可以合理地包含state或者至少是推斷state,因此block真正是物件的一個屬性。
讓我們思考一下一個有趣的場景,如果向一個block提問:“What do you think about Bob?”。block可能會做兩件事情:傳送一個訊息去捕獲物件並詢問這個物件怎麼看待Bob,或者直接返回一個捕獲的值。如果返回了一個物件的響應,我們應該越過這個block直接獲取這個物件。如果它返回了一個捕獲的值,那麼這應該是一個物件的屬性。
從以上的觀察,我們可以得出結論:如果物件的請求帶有附加資訊,更應該使用delegation
4.過程 vs 結果(Process vs. Results)
如果檢視NSURLConnectionDelegate 以及 NSURLConnectionDataDelegate,我們在可以protocol中看到這樣的訊息:我將要做什麼(如: willSendRequest,將要傳送請求)、到目前為止我知道的資訊(如:canAuthenticateAgainstProtectionSpace)、我已經完成這些啦( didReceiveResponse,收到請求的回覆,即完成請求)。這些訊息組成一個流程,而那些對流程感興趣的delegate將會在每一步得到相應的通知。
當我們觀察handler和完整的方法時,我們發現一個block包含一個響應物件和一個錯誤物件。顯然這裡沒有任何有關“我在哪裡,我正在做什麼的”的互動。
因此我們可以這樣認為,delegate的回撥更多的程式導向,而block則是面向結果的。如果你需要得到一條多步程式的通知,你應該使用delegation。而當你只是希望得到你請求的資訊(或者獲取資訊時的錯誤提示),你應該使用block。(如果你結合之前的3個結論,你會發現delegate可以在所有事件中維持state,而多個獨立的block確不能)
從上面我們可以得出兩個關鍵點。首先,如果你使用block去請求一個可能失敗的請求,你應當只使用一個block。我們可以看到如下的程式碼:
1 2 3 4 5 |
[fetcher makeRequest:^(id result) { // do something with result } error:^(NSError *err) { // Do something with error }]; |
上面程式碼的可讀性明顯比下面block的可讀性差(作者說這個是他不謙虛的觀點,其實個人認為沒有那麼嚴重)
1 2 3 4 5 6 |
[fetcher makeRequest:^(id result) { // do something with result } error:^(NSError *err) { // Do something with error }]; |