前言
鏈式呼叫(chained calls)是
指在函式呼叫返回了一個物件的時候,使得這個呼叫鏈可以不斷的呼叫下去。從概念上可以看做是一環扣一環的鐵鏈,也能被稱作方法鏈呼叫。
假設需求是在網路請求完成之後先篩選過期資料,然後轉換成對應的資料模型進行展示。在Swift
中可以直接這麼寫:
1 2 |
let dataArr = result["data"] as! [Dictionary] self.models = dataArr.filter{ $0["status"] == "1" }.map{ Model($0) } |
而OC
的語法決定了這步操作不能像Swift
一樣簡潔,最常見的程式碼就是這樣:
1 2 3 4 5 6 7 8 |
NSArray * dataArr = result[@"data"]; NSMutableArray * models = @[].mutableCopy; for (NSDictionary * dict in dataArr) { if ([dict[@"status"] isEqualToString: @"1"]) { [models addObject: [[Model alloc] initWithDict: dict]]; } } self.models = [NSArray arrayWithArray: models]; |
對比兩段程式碼,不難看出方法鏈呼叫的優點包括:
- 程式碼簡潔優雅,可讀性強
- 減少了重複使用同一變數的程式碼量
- 把複雜的操作分割成多個小操作連續呼叫
Swift
的特性決定了其在鏈式呼叫上的先天優勢,但是有沒有辦法讓OC
也能擁有這種鏈式呼叫的特性呢?答案是毫無疑問的,block
是一種非常優秀的機制,允許我們使用點語法的方式呼叫屬性block
其他要求
實現鏈式呼叫做法並不複雜,但是符合這些要求會讓你用起來更加得心應手。譬如:
- 對
block
有過足夠深的使用和了解 - 對
retain cycle
深惡痛疾,網上很多教程實際上存在著迴圈引用的問題 - 升級到
Xcode8.3
以上的版本,理由無他,加強了屬性block
呼叫的程式碼聯想
其中第三點是筆者最推崇的要求,要知道8.3
之前的鏈式封裝多多少少吃了不少程式碼聯想的苦頭
醜陋的資料來源
UITableView
是個非常牛逼的控制元件,但是對於開發者而言也並不是那麼的友善,甚至有些醜陋。實現一次tableView
的代理起碼要有以下程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#pragma mark - UITableViewDataSource - (NSInteger)tableView: (UITableView *)tableView numberOfRowsInSection: (NSInteger)section { return self.dataSource.count; } - (UITableViewCell *)tableView: (UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath { UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier: @"reuseIdentifier"]; /// configure cell return cell; } #pragma mark - UITableViewDelegate - (void)tableView: (UITableView *)tableView didSelectRowAtIndexPath: (NSIndexPath *)indexPath { /// do something } |
Protocol
是一種非常優雅的設計方案,這點是毋庸置疑的。但即便再優雅的程式碼設計,在重複的程式碼面前,也會讓人感到醜陋、厭倦。
block
相較起代理,是一種更加強大的機制。比前者更解耦更簡潔,當然兩者的優劣比較不是本文要討論的問題,通過block
呼叫的返回物件,大可以給NSArray
實現這樣的程式碼:
1 2 3 4 5 6 |
NSArray * dataArr = result[@"data"]; self.models = dataArr.filter(^BOOL(NSDictionary * dict) { return [dict[@"status"] isEqualToString: @"1"]; }).map(^id(NSDictionary * dict) { return [[Model alloc] initWithDict: dict]; }); |
雖然程式碼簡潔性上仍然差了Swift
一籌,但是相較起原執行程式碼邏輯性跟可讀性都強了不少,這部分的程式碼詳見由淺至深學習block
鏈式資料來源的實現
由於誰都可能是UITableView
的資料來源物件,那麼最粗暴的做法是為NSObject
提供一個category
用來實現相關的資料來源並且動態新增屬性。但是作為有追求的攻城獅,我們有追求,要優雅,所以這種方式直接cut
掉
UITableView
的繁雜程式碼一直是熱議的話題之一,在不同的程式碼架構中有不同的解決方案,比如MVVM
中封裝一個專門的VM
來統一處理這部分邏輯。因此可以提供一個物件作為實際資料來源物件跟UITableView
之間的中介
。由中介
實現相關協議方法,然後從實際資料來源物件方獲取資料
中介被我命名為LXDTableViewProtocolHelper
,為了保證能夠生成方法鏈,所有的屬性block
應當返回中介物件:
1 2 3 4 5 |
@class LXDTableViewProtocolHelper; typedef LXDTableViewProtocolHelper *(^TVNumberOfSections)(NSInteger(^)(void)); typedef LXDTableViewProtocolHelper *(^TVRowsNumberAtSection)(NSInteger(^)(NSInteger section)); typedef LXDTableViewProtocolHelper *(^TVDidSelectedCellHandle)(void(^)(NSIndexPath * index)); typedef LXDTableViewProtocolHelper *(^TVConfigureCell)(void(^)(__kindof UITableViewCell * cell, NSIndexPath * index)); |
typedef LXDTableViewProtocolHelper (^TVBindAndRegister)(UITableView tableView, Class cellCls, BOOL isNib, NSString * reuseIdentifier);
1 2 3 4 5 6 7 8 9 |
@interface LXDTableViewProtocolHelper : NSObject @property (nonatomic, readonly) TVConfigureCell configurateCell; @property (nonatomic, readonly) TVBindAndRegister bindTableView; @property (nonatomic, readonly) TVNumberOfSections sectionsNumber; @property (nonatomic, readonly) TVRowsNumberAtSection rowsNumber; @property (nonatomic, readonly) TVDidSelectedCellHandle didSelectedCell; @end |
使用只讀屬性修飾block
之後我們只需重寫getter
方法返回對應的處理就行了,拿rowsNumber
做個例子,按照網上很多教程都會這麼寫:
1 2 3 4 5 6 |
- (TVRowsNumberAtSection)rowsNumber { return ^LXDTableViewProtocolHelper *(NSInteger(^numberOfRowsInSection)(NSInteger section)) { self.numberOfRowsInSection = numberOfRowsInSection; return self; }; } |
但是實際上這個返回的block
是__NSMallocBlock__
型別的,這意味著這種做法會讓中介被引用。當然筆者沒去測試中介是否能正確釋放而是直接採用__weak
做法,感興趣的讀者可以重寫dealloc
來檢測。最後tableView
的協議方法就能被這樣實現:
1 2 3 4 5 6 7 8 9 10 |
- (void)configureTableViewProtocol { WeakDefine self.listHelper.bindTableView(_list, [UITableViewCell class], NO, @"cell").rowsNumber(^NSInteger(NSInteger section) { return weakself.models.count; }).configurateCell(^(SingleTitleCell * cell, NSIndexPath * index) { cell.titleLabel.text = weakself.models[index.row]; }).didSelectedCell(^(NSIndexPath *index) { [weakself updateInfoWithModel: weakself.models[index.row]]; }); } |
更多
得益於強大的block
,即便OC
沒有Swift
那麼優雅的高階函式,依舊能夠實現讓程式碼緊湊已讀,當然也會提高debug
的難度。除了將資料來源鏈式之外,你還可以嘗試把網路請求進行封裝,做成鏈式處理,比如筆者的請求程式碼:
1 2 3 4 5 |
Get(Component(@"user/getUserInfo", nil)).then(^(NSDictionary * result) { /// request success }).failed(^(NSError * error) { /// request failed }).start(); |