在 iOS 中實現方法鏈呼叫
前言
鏈式呼叫(chained calls)是
指在函式呼叫返回了一個物件的時候,使得這個呼叫鏈可以不斷的呼叫下去。從概念上可以看做是一環扣一環的鐵鏈,也能被稱作方法鏈呼叫。
假設需求是在網路請求完成之後先篩選過期資料,然後轉換成對應的資料模型進行展示。在Swift
中可以直接這麼寫:
12 | let dataArr=result["data"]as![Dictionary]self.models=dataArr.filter{$0["status"]=="1"}.map{Model($0)} |
而OC
的語法決定了這步操作不能像Swift
一樣簡潔,最常見的程式碼就是這樣:
12345678 | NSArray *dataArr=result[@"data"];NSMutableArray *models=@[].mutableCopy;for(NSDictionary *dict indataArr){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
的代理起碼要有以下程式碼:
123456789101112131415 | #pragma mark - UITableViewDataSource-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{returnself.dataSource.count;}-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:@"reuseIdentifier"];/// configure cellreturncell;}#pragma mark - UITableViewDelegate-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{/// do something} |
Protocol
是一種非常優雅的設計方案,這點是毋庸置疑的。但即便再優雅的程式碼設計,在重複的程式碼面前,也會讓人感到醜陋、厭倦。
block
相較起代理,是一種更加強大的機制。比前者更解耦更簡潔,當然兩者的優劣比較不是本文要討論的問題,通過block
呼叫的返回物件,大可以給NSArray
實現這樣的程式碼:
123456 | 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
應當返回中介物件:
12345 | @classLXDTableViewProtocolHelper;typedefLXDTableViewProtocolHelper *(^TVNumberOfSections)(NSInteger(^)(void));typedefLXDTableViewProtocolHelper *(^TVRowsNumberAtSection)(NSInteger(^)(NSInteger section));typedefLXDTableViewProtocolHelper *(^TVDidSelectedCellHandle)(void(^)(NSIndexPath *index));typedefLXDTableViewProtocolHelper *(^TVConfigureCell)(void(^)(__kindof UITableViewCell *cell,NSIndexPath *index)); |
typedef LXDTableViewProtocolHelper (^TVBindAndRegister)(UITableView tableView, Class cellCls, BOOL isNib, NSString * reuseIdentifier);
123456789 | @interfaceLXDTableViewProtocolHelper: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
做個例子,按照網上很多教程都會這麼寫:
123456 | -(TVRowsNumberAtSection)rowsNumber{return^LXDTableViewProtocolHelper *(NSInteger(^numberOfRowsInSection)(NSInteger section)){self.numberOfRowsInSection=numberOfRowsInSection;returnself;};} |
但是實際上這個返回的block
是__NSMallocBlock__
型別的,這意味著這種做法會讓中介被引用。當然筆者沒去測試中介是否能正確釋放而是直接採用__weak
做法,感興趣的讀者可以重寫dealloc
來檢測。最後tableView
的協議方法就能被這樣實現:
12345678910 | -(void)configureTableViewProtocol{WeakDefineself.listHelper.bindTableView(_list,[UITableViewCell class],NO,@"cell").rowsNumber(^NSInteger(NSInteger section){returnweakself.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
的難度。除了將資料來源鏈式之外,你還可以嘗試把網路請求進行封裝,做成鏈式處理,比如筆者的請求程式碼:
12345 | Get(Component(@"user/getUserInfo",nil)).then(^(NSDictionary *result){/// request success}).failed(^(NSError *error){/// request failed}).start(); |