编译:伯乐在线 - 林欣达

如有好文章投稿,请点击 → 这里了解详情

如需转载,发送「转载」二字查看说明

前言

链式调用(chained calls)是指在函数调用返回了一个对象的时候,使得这个调用链可以不断的调用下去。从概念上可以看做是一环扣一环的铁链,也能被称作方法链调用。

假设需求是在网络请求完成之后先筛选过期数据,然后转换成对应的数据模型进行展示。在Swift中可以直接这么写:

let dataArr = result["data"] as! [Dictionary]

self.models = dataArr.filter{ $0["status"] == "1" }.map{ Model($0) }

而OC的语法决定了这步操作不能像Swift一样简洁,最常见的代码就是这样:

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的代理起码要有以下代码:

#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实现这样的代码:

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(http://www.jianshu.com/p/29d70274374b)

链式数据源的实现

由于谁都可能是UITableView的数据源对象,那么最粗暴的做法是为NSObject提供一个category用来实现相关的数据源并且动态添加属性。但是作为有追求的攻城狮,我们有追求,要优雅,所以这种方式直接cut掉

UITableView的繁杂代码一直是热议的话题之一,在不同的代码架构中有不同的解决方案,比如MVVM中封装一个专门的VM来统一处理这部分逻辑。因此可以提供一个对象作为实际数据源对象跟UITableView之间的中介。由中介实现相关协议方法,然后从实际数据源对象方获取数据

中介被我命名为LXDTableViewProtocolHelper,为了保证能够生成方法链,所有的属性block应当返回中介对象:

@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);

@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做个例子,按照网上很多教程都会这么写:

- (TVRowsNumberAtSection)rowsNumber {

return ^LXDTableViewProtocolHelper *(NSInteger(^numberOfRowsInSection)(NSInteger section)) {

self.numberOfRowsInSection = numberOfRowsInSection;

return self;

};

}

但是实际上这个返回的block是__NSMallocBlock__类型的,这意味着这种做法会让中介被引用。当然笔者没去测试中介是否能正确释放而是直接采用__weak做法,感兴趣的读者可以重写dealloc来检测。最后tableView的协议方法就能被这样实现:

- (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的难度。除了将数据源链式之外,你还可以尝试把网络请求进行封装,做成链式处理,比如笔者的请求代码:

Get(Component(@"user/getUserInfo", nil)).then(^(NSDictionary * result) {

/// request success

}).failed(^(NSError * error) {

/// request failed

}).start();

最新文章

  1. java-测试synchronized使用xxx.class和this使用的区别
  2. oracle学习 一 (持续更新中)
  3. 发现一个好的开源项目:lomoX(挑着看,每天看一点,看一年就ok了)——用Webkit开发桌面软件,炫
  4. sql语句相关整理
  5. Mvc学习笔记(4)
  6. mysql性能调优与架构设计(一)商业需求与系统架构对性能的影响
  7. 在Centos7 更改Docker默认镜像和容器的位置
  8. 接口测试思路,jmeter,接口测试流程
  9. sql server2012远程连接用IP登陆进入设置步骤
  10. mac环境下支持PHP调试工具xdebug,phpstorm监听
  11. App架构师实践指南四之性能优化一
  12. Git rebase的使用
  13. 吴裕雄 24-MySQL 索引
  14. 初学Linux(一)关闭操作shutdown halt reboot
  15. 玩Web虎-运行时受保护文件不可复制
  16. ngRouter和ui-router区别
  17. statsvn,代码统计
  18. Python List+Tuple+Dict+Set小结
  19. TCP系列35—窗口管理&流控—9、紧急机制
  20. Homebrew 的使用

热门文章

  1. Linux下配置nfs并远程挂载
  2. Centos配置为驱动程序开发环境
  3. 腾讯下载的视频转换为MP4
  4. Cognos11中报XQE-JDB-0004查找驱动程序类错误
  5. Linux网络编程:基于TCP的程序开发回顾篇《转》
  6. Apache Kafka学习 (一)
  7. LintCode: Flatten Binary Tree to Linked List
  8. PAT 1065 1066 1067 1068
  9. C#中使用RabbitMQ收发队列消息
  10. Java Spring Quartz 定时任务