说到block,相信大部分iOS开发者都会想到retain cycle或是__block修饰的变量。

但是本文将忽略这些老生常谈的讨论,而是将重点放在美团iOS在实践中对block的应用,希望能对同行有所助益。

本文假设读者对block有一定的了解。

从闭包说起

在Lisp这样的语言中,有一个概念叫做闭包(closure1),指的是一个函数以及它所处的词法作用域(lexical scope2)构成的整体。为了理解闭包,我们首先来看看什么是词法作用域。

所谓词法作用域,顾名思义,是指一个符号引用的是其词法环境中的变量,而无关程序在运行时的状态。这么说可能有点抽象,让我来看一段Common Lisp3代码:

(defvar printer (let ((x 42))(lambda () (format t "~a" x))))

这里我们定义了一个变量printer,它的值是一个函数,这个函数会打印词法作用域中的变量x(其值为42)。

现在我们来调用这个函数:

CL-USER> (funcall printer)
42

可以看到,我们调用了printer中存放的函数之后,打印出来的数字是42,跟我们的预期相符。

接下来再让我们看一个可能会出乎意料的结果:

CL-USER> (let ((x 1))(funcall printer))
42

我们在调用之前把x设置为了1,但是打印的结果仍然是42。

为什么?因为printer中存放的函数在被调用时所引用的变量位于其词法作用域中, 即该函数被定义时所处的词法环境中,所以程序在运行时设置的变量x对函数不起作用。

前面我们讲过,所谓闭包,就是函数及其词法作用域的合称,具体到上例,那么匿名函数和x就构成了一个闭包,它会为函数保存一种状态,有点类似于全局变量,不过除了那个匿名函数,其他函数无法访问到x。

说了这么多,似乎跟block毫无关系?事实上,block为C带来了闭包。

Block

Apple从OS X 10.6和iOS 4以后开始支持block,让我们用C把上面的例子重写一下:

#include <stdio.h>int main ()
{int x = 42;void (^block)() = ^() {printf("%d\n", x);};block();x = 1;block();return 0;
}

编译运行后得到的输出同样是两个42。

到了这里,相信读者对闭包已经有一个直观的认识了,但是它有什么用?有什么好处?

设想如下场景,我们要请求一个URL,并以block的形式传入回调函数,并在回调函数中用到刚才这个URL:

NSURL *someURL = …;
[SomeClass getURL:someURL finished:^(id responseObject) {// process responseObject with someURL
}];

这里网络请求是异步的,所以当block中代码执行时,getURL:finished:方法调用所在的栈很可能已经不存在了,但是因为回调block和someURL构成了closure,所以即使栈不存在,block仍然可以引用到someURL。

可能你会说,“我在block中增加一个NSURL类型的参数,把someURL传回来不也可以实现同样的目的吗?”不妨设想如果我们在block中要引用的对象有10个之多,用参数列表传递明显不再现实,用容器类或者专门定义一个类来传递虽然可以,但是前者没有编译器为我们检查错误,后者则相当繁琐。而利用闭包,可以轻易达到灵活性和简洁性的平衡。事实上,美团客户端就大量利用了闭包,在UI层发出请求,在回调中更新某些UI组件。

函数式编程4

在Lisp中,函数是一等公民,可以随时创建、作为参数传递、作为返回值返回,Objective C在没有block之前,没有类似的机制,有了block,Objective C也就具备了函数式编程的能力,block是对象,有自己的ISA指针,可以随时创建,作为参数传递,作为返回值返回。

先来看看block的经典用法:

[UIView animateWithDuration:0.25 animations:^{self.view.alpha = 1.0f;}];

UIView的animateWithDuration:animations:方法的第二个参数是一个block,它把跟动画相关的操作封装起来传递进去,以实现动画效果。

现在让我们发掘一下类似的用法:

[SAKBaseModel comboRequest:^() {[dealModel fetchDealByID:123456withFields:nilcompletion:^(MTDeal *deal, NSError *error) {...}];[orderModel fetchOrderByID:654321withDealFields:nilcompletion:^(MTOrder *order, NSError *error) {...}];
}];

这里我们为SAKBaseModel设计了一个类似于UIView的接口叫comboRequest,它会接受一个block作为参数,在这个block中发出的请求都会作为combo请求的一部分。如果dealModel或者orderModel的任何一个请求不是出现在block中,那么它就是一个普通的请求。这样做的好处是dealModel和orderModel的接口不需要关心自己是不是属于一个combo请求,调用者则可以灵活地调整代码。

那么怎么实现这样的接口呢?还是从UIView上获取灵感。我们知道UIView有个方法setAnimationsEnabled:,实际上SAKBaseModel也可以有这么一个方法:setComboRequestEnabled:,而在comboRequest方法的实现中,在调用传进来的block之前先setComboRequestEnabled:YES,调用完后再恢复为原状态。相应的,在实际的model接口中,检查comboRequest是否为YES,如果是,则把自己作为一个combo请求的一部分,否则正常发出请求即可。

Think Big

Lisp最强大的特性之一是condition系统,它可以分离异常的检测、异常的解决和异常解决方式的决策,看一段示例代码:

(define-condition network-timeout-error (error)((url :initarg :url :accessor url)))(defun try-again (condition)(let ((restart (find-restart ‘try-again)))(when restart (invoke-restart restart))))(defun deal-requester (deal-id)(handler-bind ((network-timeout-error #’try-again))(request-from-url (format nil “http://api.mobile.meituan.com/deal/~a” deal-id)(lambda (deal error)(if error(format t “error: ~a”, error)(process-deal))))))
(defun request-from-url (url finished)(let ((callback (lambda (response error)(if (network-timeout-error-p error)(error ‘network-timeout-error :url url)(funcall finished (parse-deal response) error)))))(restart-bind((try-again (lambda () (http-request url callback))))(http-request url callback))))

可以看到,condition系统对于代码的分层提供了良好的支持,请求超时的错误在底层代码被检测到,在发出请求前注册一个restart,而在业务层去决定要不要调用restart。

一直以来,C语言要实现优雅的异常处理就是一件不简单的事情,而Objective-C虽然加入了try-catch支持,但是苹果并不鼓励使用,那么能否实现类似于condition系统这样的异常处理机制呢?

答案是能。让我们来看看接口设计:

typedef void (^RESTART)(id userInfo);
typedef void (^HANDLER)(id condition);void restart_bind(void (^body)(), NSString *restartName, RESTART restart, ...) NS_REQUIRES_NIL_TERMINATION;void handler_bind(void (^body)(), Class class, HANDLER handler, ...) NS_REQUIRES_NIL_TERMINATION;void notify(id condition);RESTART find_restart(NSString *restartName);

如下图所示,handler_bind首先在栈中注册好handler,而restart_bind则在handler有效的环境中注册restart,当有异常发生时,notify函数会在当前环境中寻找handler,找到后,控制会转移到上层的handler代码中,这时handler可以用find_restart在栈中搜索restart,找到之后可以调用,从而实现异常的恢复,做完这一切,控制回到notify发生的点继续向下执行。

完整的代码敬请期待美团iOS的开源项目。

有了SAKCondition,我们可以实现任意底层代码的逻辑穿透到上层代码,比如网络层和UI层,使得上层代码可以在不了解下层代码实现细节的情况下调用恢复机制。事实上,美团的iPhone客户端就是利用SAKCondition实现了美团账户的安全解锁功能。

总结

block给Objective C带来了无穷的可能性。本文只讨论了美团iOS在实践中的一些用法,更多想法还在等待挖掘。

block在美团iOS的实践相关推荐

  1. Presto实现原理和美团的使用实践

     Presto实现原理和美团的使用实践 Facebook的数据仓库存储在少量大型Hadoop/HDFS集群.Hive是Facebook在几年前专为Hadoop打造的一款数据仓库工具.在以前,Fac ...

  2. 【测试开发】自动化测试在美团外卖的实践与落地

    文章目录 自动化测试在美团外卖的实践与落地 1.项目背景 2.项目目标 3.方案选型 4.实践和探索 4.1 问题和挑战 4.2 前置条件准备 4.3 用例录制与回放的数据一致性 4.4 用例录制与回 ...

  3. 美团 iOS 工程 zsource 命令背后的那些事儿

    zsource 命令是什么? 美团 App 在 2015 年就已经基于 CocoaPods 完成了组件化的工作.在组件化的改造过程中,为了能够加速整体工程的构建速度,我们对需要集成进美团 App 的组 ...

  4. 高仿美团iOS版,版本号5.7

    高仿美团iOS版,版本号:5.7 iOS技术交流群:112365317 github链接:https://github.com/lookingstars/meituan 如果你觉得不错,欢迎star ...

  5. 【免费报名】技术沙龙第22期:美团点评AI实践

    美团点评技术沙龙由美团点评技术团队主办,每期沙龙邀请美团点评及其他互联网公司的技术专家分享来自一线的实践经验,覆盖各主要技术领域. 最近来,随着硬件计算能力的增强,以及大数据的爆破式增长,深度学习技术 ...

  6. KDD Cup 2020 自动图学习比赛冠军技术方案及在美团广告的实践

    ACM SIGKDD (国际数据挖掘与知识发现大会,简称 KDD)是数据挖掘领域的国际顶级会议. 美团到店广告平台搜索广告算法团队基于自身的业务场景,一直在不断进行前沿技术的深入优化与算法创新.团队的 ...

  7. 美团 iOS 端开源框架 Graver 在动态化上的探索与实践

    近些年,移动端动态化技术可谓是"百花齐放",其中的渲染性能也是动态化技术一直在探索.研究的课题.美团的开源框架 Graver 也为解决动态化框架的渲染性能问题提供了一种新思路:关于 ...

  8. iOS App冷启动治理:来自美团外卖的实践

    一.背景 冷启动时长是App性能的重要指标,作为用户体验的第一道"门",直接决定着用户对App的第一印象.美团外卖iOS客户端从2013年11月开始,历经几十个版本的迭代开发,产品 ...

  9. 自动化测试在美团外卖的实践与落地

    随着美团到家业务的发展,系统复杂度也在持续增长.测试用例数量近两年增长约一倍,单端数量超过1万2千条,而研发人员的工作从大部分时间在开发,转变成一半时间在开发.一半时间在模拟环境和自测.因此,引入自动 ...

最新文章

  1. Python爱好者周知:Scikit-Learn中文文档正式发布
  2. 腾讯某员工哀叹:门口卖早点的送孩子去私立了,一年学费顶我一年工资!
  3. SizeBasedAndTimeBasedLog
  4. Luogu4451 [国家集训队]整数的lqp拆分
  5. 正则表达式(RegularExpression)
  6. nginx proxy svn 加快_Hexo+Nginx+SVN搭建网站
  7. 网关限流(令牌桶算法)
  8. 如何检查PAL安装的功能
  9. idea 调试远程tomcat
  10. java 创建Reader_java – 最佳实践:为XMLReader创建SAX解析器
  11. javaEE版eclipse设置默认编码格式为utf-8
  12. 量子链创始人:监管者应看到区块链科技的大局
  13. Java爬虫高级教程-动力节点
  14. kdj线指标详解与案例探究
  15. 计算机u盘病毒清除方式,终极:如何消除计算机上U盘的“文件夹.EXE病毒”?
  16. draco压缩引擎学习笔记(二)
  17. 苹果mac休眠快捷键_有人说 Mac 系统不适合搞科研,我笑了:还不是因为你不会用!...
  18. 使用RMAN备份时应如何处置归档日志文件
  19. DEDECMS织梦中华美食网早期版本菜谱网整站源码
  20. vue兼容IE 360

热门文章

  1. AutoIt: WinGetClassList可以把当前窗口所有的handle全部列出来
  2. linux 编译安装apache
  3. “函数调用的左操作数”的理解
  4. linux 命令学习记录
  5. 屏蔽非法路由,好好上网!
  6. “元宇宙” 是什么东西?
  7. 给不起钱的大公司,算不上大公司
  8. 用bat-抓取android日志
  9. 我这几年呆的这几个公司
  10. 每日一题(2)—— -2与2的比较