编译:伯乐在线 - 林欣达

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

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

前言

链式调用(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 中实现方法链调用的更多相关文章

  1. iOS 中 h5 页面 iframe 调用高度自扩展问题及解决

    开发需求需要在 h5 中用 iframe 中调用一个其他公司开发的 html 页面. 简单的插入 <iframe /> 并设置宽高后,发现在 Android 手机浏览器上打开可以正常运行, ...

  2. iOS中NSTimer的invalidate调用之后

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 免责申明:本博客提供的所有翻译文章原稿均来自互联网,仅供学习交 ...

  3. ios中layoutsubview何时被调用

    layoutsubview和viewDidlayoutsubview(控制器)被调用的集中情况 一:当view的frame或bounds发生改变 1:直接改view的frame或bounds 会调用v ...

  4. 【iOS和HTML 5交互】iOS中加载html5调用html方法和修改html5内容

    近期项目开发中用到了这方面的技术了,那我们一起来看看. 1.利用webView控件加载本地html5或者网络上html5 2.设置控制器为webView的代理,遵守协议 3.实现代理方法webView ...

  5. ios 中NSString的一些调用

    #import <Foundation/Foundation.h> int main(int argc, const char * argv[]) {    @autoreleasepoo ...

  6. ios中摄像头和图片调用

    推荐文章 http://www.xuanyusong.com/archives/1493 http://blog.csdn.net/ryantang03/article/details/7830996

  7. IOS中调用系统的电话、短信、邮件、浏览功能

    iOS开发系列--通讯录.蓝牙.内购.GameCenter.iCloud.Passbook系统服务开发汇总 2015-01-13 09:16 by KenshinCui, 26990 阅读, 35 评 ...

  8. ES6 Promise对象then方法链式调用

    then()方法的作用是Promise实例添加解决(fulfillment)和拒绝(rejection)状态的回调函数.then()方法会返回一个新的Promise实例,所以then()方法后面可以继 ...

  9. Unity在Android和iOS中如何调用Native API

    本文主要是对unity中如何在Android和iOS中调用Native API进行介绍. 首先unity支持在C#中调用C++ dll,这样可以在Android和iOS中提供C++接口在unity中调 ...

随机推荐

  1. 转:从头开始编写基于隐含马尔可夫模型HMM的中文分词器

    http://blog.csdn.net/guixunlong/article/details/8925990 从头开始编写基于隐含马尔可夫模型HMM的中文分词器之一 - 资源篇 首先感谢52nlp的 ...

  2. 阿里巴巴Java开发规约插件全球首发!(转)

    https://mp.weixin.qq.com/s?__biz=MzI0NTE4NjA0OQ==&mid=2658355901&idx=1&sn=3169172bfc6819 ...

  3. Web安全学习规划

    一名合格的Web安全工程师是要具备很多的知识点,不但要对网站架构熟悉,通讯协议,测试流程与测试工具使用,漏洞利用脚本编写,还有需要经验的积累等. 互联网进入下半场,竞争越发的激烈,能与人工智能比肩的热 ...

  4. 一次jdbc乱码解决

    今天我做了一个小实验,从sqlserver 2010中将一张表转移到mysql中,使用的是基本的jdbc,前面复制的好好地,不知道怎么了,到了第三万行,突然出现了下面的异常 Incorrect str ...

  5. POJ 3009:Curling 2.0 推箱子

    Curling 2.0 Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 14090   Accepted: 5887 Desc ...

  6. [海蜘蛛] 海蜘蛛 V8 全线无限试用版 免费发布破解教程

    http://bbs.p52.cn/forum.php?mod=viewthread&tid=3499&extra=page%3D1&page=1&_dsign=79c ...

  7. yii源码二 -- interfaces

    path:framework/base/interfaces.php overview:This file contains core interfaces for Yii framework. in ...

  8. 【转】开发者可以使用Docker做什么?

    有些开发者可能还是不明白 Docker 对自己到底有多大的用处,因此翻译 Docker 个人用例 这篇文章中来介绍 Docker 在普通开发者开发过程中的用例. Docker 如今赢得了许多关注,很多 ...

  9. 老周发布 UWP 应用的隐私策略(通用)

    UWP 应用隐私策略 前注  本声明通用于老周所发布的所有 UWP 应用,下文简称“应用”.开发者全称:周家安,下文简称“老周”. 1.免责声明 您在使用应用过程中,请遵守<中华人民共和国宪法& ...

  10. poj2689 Prime Distance 有难度 埃拉托斯尼斯筛法的运用

    我承认这道很难(对我来说),搞脑子啊,搞了好久,数论刚开始没多久,还不是很强大,思路有点死,主要是我 天赋太差,太菜了,希望多做做有所改善 开始解析: 首先要将在 [ l,u]内的所有素数找出来,还好 ...