在 iOS 中实现方法链调用
编译:伯乐在线 - 林欣达
如需转载,发送「转载」二字查看说明
前言
链式调用(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();
在 iOS 中实现方法链调用的更多相关文章
- iOS 中 h5 页面 iframe 调用高度自扩展问题及解决
开发需求需要在 h5 中用 iframe 中调用一个其他公司开发的 html 页面. 简单的插入 <iframe /> 并设置宽高后,发现在 Android 手机浏览器上打开可以正常运行, ...
- iOS中NSTimer的invalidate调用之后
大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 免责申明:本博客提供的所有翻译文章原稿均来自互联网,仅供学习交 ...
- ios中layoutsubview何时被调用
layoutsubview和viewDidlayoutsubview(控制器)被调用的集中情况 一:当view的frame或bounds发生改变 1:直接改view的frame或bounds 会调用v ...
- 【iOS和HTML 5交互】iOS中加载html5调用html方法和修改html5内容
近期项目开发中用到了这方面的技术了,那我们一起来看看. 1.利用webView控件加载本地html5或者网络上html5 2.设置控制器为webView的代理,遵守协议 3.实现代理方法webView ...
- ios 中NSString的一些调用
#import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { @autoreleasepoo ...
- ios中摄像头和图片调用
推荐文章 http://www.xuanyusong.com/archives/1493 http://blog.csdn.net/ryantang03/article/details/7830996
- IOS中调用系统的电话、短信、邮件、浏览功能
iOS开发系列--通讯录.蓝牙.内购.GameCenter.iCloud.Passbook系统服务开发汇总 2015-01-13 09:16 by KenshinCui, 26990 阅读, 35 评 ...
- ES6 Promise对象then方法链式调用
then()方法的作用是Promise实例添加解决(fulfillment)和拒绝(rejection)状态的回调函数.then()方法会返回一个新的Promise实例,所以then()方法后面可以继 ...
- Unity在Android和iOS中如何调用Native API
本文主要是对unity中如何在Android和iOS中调用Native API进行介绍. 首先unity支持在C#中调用C++ dll,这样可以在Android和iOS中提供C++接口在unity中调 ...
随机推荐
- 转:从头开始编写基于隐含马尔可夫模型HMM的中文分词器
http://blog.csdn.net/guixunlong/article/details/8925990 从头开始编写基于隐含马尔可夫模型HMM的中文分词器之一 - 资源篇 首先感谢52nlp的 ...
- 阿里巴巴Java开发规约插件全球首发!(转)
https://mp.weixin.qq.com/s?__biz=MzI0NTE4NjA0OQ==&mid=2658355901&idx=1&sn=3169172bfc6819 ...
- Web安全学习规划
一名合格的Web安全工程师是要具备很多的知识点,不但要对网站架构熟悉,通讯协议,测试流程与测试工具使用,漏洞利用脚本编写,还有需要经验的积累等. 互联网进入下半场,竞争越发的激烈,能与人工智能比肩的热 ...
- 一次jdbc乱码解决
今天我做了一个小实验,从sqlserver 2010中将一张表转移到mysql中,使用的是基本的jdbc,前面复制的好好地,不知道怎么了,到了第三万行,突然出现了下面的异常 Incorrect str ...
- POJ 3009:Curling 2.0 推箱子
Curling 2.0 Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 14090 Accepted: 5887 Desc ...
- [海蜘蛛] 海蜘蛛 V8 全线无限试用版 免费发布破解教程
http://bbs.p52.cn/forum.php?mod=viewthread&tid=3499&extra=page%3D1&page=1&_dsign=79c ...
- yii源码二 -- interfaces
path:framework/base/interfaces.php overview:This file contains core interfaces for Yii framework. in ...
- 【转】开发者可以使用Docker做什么?
有些开发者可能还是不明白 Docker 对自己到底有多大的用处,因此翻译 Docker 个人用例 这篇文章中来介绍 Docker 在普通开发者开发过程中的用例. Docker 如今赢得了许多关注,很多 ...
- 老周发布 UWP 应用的隐私策略(通用)
UWP 应用隐私策略 前注 本声明通用于老周所发布的所有 UWP 应用,下文简称“应用”.开发者全称:周家安,下文简称“老周”. 1.免责声明 您在使用应用过程中,请遵守<中华人民共和国宪法& ...
- poj2689 Prime Distance 有难度 埃拉托斯尼斯筛法的运用
我承认这道很难(对我来说),搞脑子啊,搞了好久,数论刚开始没多久,还不是很强大,思路有点死,主要是我 天赋太差,太菜了,希望多做做有所改善 开始解析: 首先要将在 [ l,u]内的所有素数找出来,还好 ...