1. 程式人生 > >在 iOS 中實現方法鏈呼叫

在 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();