39.用handler塊降低代碼分散程度

《編寫高質量iOS與OS X代碼的52個有效方法》--第六章 第39條
(ps:此乃讀書筆記,加深記憶,僅供大家參考)


第39條:用handler塊降低代碼分散程度

為用戶界面編碼時,一種常用的范式就是“異步執(zhí)行任務”(perform task asynchro nously)。這種范式的好處在于:處理用戶界面的顯示及觸摸操作所用的線程,不會因為要執(zhí)行I/O或網(wǎng)絡通信這類耗時的任務而阻塞。這個線程通常稱為主線程(main thread)。某些情況下,如果應用程序在一定時間內無響應,那么就會自動終止?!跋到y(tǒng)監(jiān)控器”(system watchdog)在發(fā)現(xiàn)某個應用程序的主線程已經(jīng)阻塞了一段時間之后,就會令其終止。

異步方法在執(zhí)行完任務之后,需要以某種手段通知相關代碼。實現(xiàn)此功能有很多辦法。常用的技巧是設計一個委托協(xié)議(參見第23條),令關注此事的對象遵從該協(xié)議。對象成為delegate之后,就可以在相關事件發(fā)生時(例如某個異步任務執(zhí)行完畢時)得到通知了。

如果改用塊來寫的話,代碼會更清晰。塊可以令這種API變得更緊致,同時也令開發(fā)者調用起來更加方便。

typedef void (^EOCNetworkFetcherCompletionHandler)(NSData *data);

@interface EOCNetworkFetcher : NSObject
- (id)initWithURL:(NSURL *)url;
- (void)startWithCompletionHandler: (EOCNetworkFetcherCompletionHandler)handler;
@end

這和使用委托協(xié)議很像,不過多了個好處,就是可以在調用start方法時直接以內聯(lián)形式定義completion handler,以此方式來使用“網(wǎng)絡數(shù)據(jù)獲取器”(network fetcher),可以令代碼比原先易懂很多。

與使用委托模式的代碼相比,用塊寫出來的代碼閑的更為整潔。委托模式有個缺點:如果類要分別使用多個獲取器下載不同的數(shù)據(jù),那么就得在delegate回調方法里根據(jù)傳入的獲取器參數(shù)來切換。

異步執(zhí)行任務完畢后所需運行的業(yè)務邏輯,和啟動異步任務所用的代碼放在了一起。無須保存獲取器,也無需再回調方法里切換,每個completion handler的業(yè)務邏輯,都是和相關獲取器對象一起來定義的。

NSURL *url = [[NSURL alloc] initWithString:@"http:www.baidu.com"];
EOCNetworkFetcher *fetcher = [[EOCNetworkFetcher alloc] initWithURL:url];
[fetcher startWithCompletionHandler:^(NSData *data) {
    _fetchedFooData = data;
}];

這種寫法還有其他用途,比如,現(xiàn)在很多基于塊的API都使用塊來處理錯誤。可以分別用兩個處理器來處理操作失敗的情況和操作成功的情況。也可以把處理失敗情況所需的代碼,與處理正常情況所用的代碼,都封裝到同一個completion handler塊里。

采用兩個獨立的處理程序:

typedef void (^EOCNetworkFetcherCompletionHandler)(NSData *data);
typedef void (^EOCNetworkFetcherErrorHandler)(NSError *error);

@interface EOCNetworkFetcher : NSObject
- (id)initWithURL:(NSURL *)url;
- (void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHandler)completion failureHandler:(EOCNetworkFetcherErrorHandler)failure;
@end

這種API設計風格很好,由于成功和失敗的情況要分別處理,所以調用此API的代碼也就會按照邏輯,把應對成功和失敗情況的代碼分開來寫,這將令代碼更易讀懂。

把處理成功情況和失敗情況所用的代碼全放在一個塊里:

typedef void (^EOCNetworkFetcherCompletionHandler)(NSData *data, NSError *error);

@interface EOCNetworkFetcher : NSObject
- (id)initWithURL:(NSURL *)url;
- (void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHandler)completion;
@end

此種API調用方式如下:

EOCNetworkFetcher *fetcher = [[EOCNetworkFetcher alloc] initWithURL:url];
[fetcher startWithCompletionHandler:^(NSData *data, NSError *error) {
    if (error) {
        //Handler failure
    } else {
        //Handler success
    }
}];

這種方法需要在塊代碼中檢測傳入的error變量,并且要把所有邏輯代碼都放在一處。這種寫法的缺點是:由于全部邏輯都寫在一起,所以會令塊變得比較長,且比較復雜。然而只用一個塊的寫法也有好處,那就是更為靈活。

把成功情況和失敗情況放在同一個塊中,還有個優(yōu)點:調用API的代碼可能會在處理成功相應的過程中發(fā)現(xiàn)錯誤。比方說,返回的數(shù)據(jù)可能太短了。這種情況需要和網(wǎng)絡數(shù)據(jù)獲取器所認定的失敗情況按同一方式處理。此時,如果采用單一塊的寫法,那么就能把這種情況和所認定的失敗情況統(tǒng)一處理了。

總體來說,筆者建議使用同一塊來處理成功與失敗的情況,蘋果公司似乎也是這樣設計其API的。例如,Twitter框架中的TWRequest及MapKit框架中的MKLocalSearch都只是用一個handler塊。

有時需要在相關時間點執(zhí)行回調操作,這種情況也可以使用handler塊。比方說,調用網(wǎng)絡數(shù)據(jù)獲取器的代碼,也許想在每次有下載進度時都得到通知。這可以通過委托模式實現(xiàn)。不過也可以使用本節(jié)的handler塊,把處理下載進度的handler定義成塊類型,并新增一個此類型的屬性:

typedef void (^EOCNetworkFetcheProgressHandler)(float progress);

@property (nonatomic, copy) EOCNetworkFetcheProgressHandler progressHandler;

這種寫法很好,因為它還是能把所有業(yè)務邏輯都放在一起,也就是把創(chuàng)建網(wǎng)絡數(shù)據(jù)獲取器和定義progress handler所用的代碼寫在一處。

基于handler來設計API還有個原因,就是某些代碼必須運行在特定的線程上。因此,最好能由調用API的人來決定handler應該運行在那個線程上。NSNotificationCenter就屬于這種API,它提供了一個方法,調用者可以經(jīng)由此方法來注冊想要接收的通知,等到相關事件發(fā)生時,通知中心就會執(zhí)行注冊好的那個塊。調用者可以指定某個塊應該安排在哪個執(zhí)行隊列里,然而這不是必需的。若沒有指定隊列,按默認方式執(zhí)行。

- (id <NSObject>)addObserverForName:(nullable NSString *)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block

要點

  • 在創(chuàng)建對象時,可以使用內聯(lián)的handler塊將相關業(yè)務邏輯一并聲明。
  • 在有多個實例需要監(jiān)控時,如果采用委托模式,那么經(jīng)常需要根據(jù)傳入的對象來切換,而若改用handler塊來實現(xiàn),則可直接將塊與相關對象放在一起。
  • 設計API時如果用到了handler塊,那么可以增加一個參數(shù),使調用者可通過此參數(shù)來決定應該把塊安排在哪個隊列上執(zhí)行。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 用 handler 塊 降低代碼分散程度 為用戶界面編碼時, 一種常見的范式是 '異步執(zhí)行任務', 這種范式的好處...
    dingzhijie閱讀 648評論 0 1
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,568評論 19 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,057評論 25 709
  • 有時候,想想生活,其實也不過如此。不在世間營營,就已經(jīng)知足。閑時就去遠方,看看生命和死亡的詩;縱使忙碌也不對生活...
    胡然乎閱讀 175評論 0 0
  • 平凡的日子里 忽然有一天 千里之外 有個人對你說 我想你 那么 我很慶幸 我在你的世界里存在過 我也很慶幸 我會被...
    YIERLHY閱讀 299評論 0 0

友情鏈接更多精彩內容