开个新坑,来写写用 Swift 来做函数式编程的技巧。
引言:任何一个沾点
Functional
特性的框架,比如Promise,ReactiveCocoa或者RxSwift都提供了处理异步操作顺序执行的方案。
但其实用 Swift 本身自带很多Functional的特性,自己实现也并不难。
本文探讨了其中一种方法。
提起异步操作的序列执行,指的是有一系列的异步操作(比如网络请求)的执行有前后的依赖关系,前一个请求执行完毕后,才能执行下一个请求。
异步操作的定义
我们定义一般异步操作都是如下形式:
1 2 3 4 5 |
func asyncOperation(complete : ()-> Void){ //..do something complete() } |
常规的异步操作都会接受一个闭包作为参数,用于操作执行完毕后的回调。
那异步操作的序列化会有什么问题呢? 看如下的伪代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
func asyncOperation(complete : ()-> Void){ //..do something print("fst executed") complete() } func asyncOperation1(complete : ()-> Void){ //..do something print("snd executed") complete() } func asyncOperation2(complete : ()-> Void){ //..do something print("third executed") complete() } |
我们定义了三个操作asyncOperation
,asyncOperation1
和 asyncOperation2
,现在我们想序列执行三个操作,然后在执行完后输出 all executed
。 按照常规,我们就写下了如下的代码:
1 2 3 4 5 6 7 |
asyncOperation { asyncOperation1({ asyncOperation2({ print("all executed") }) }) } |
可以看到,明明才三层,代码似乎就有点复杂了,而我们真正关心的代码却只有 print("all executed")
这一行。但为了遵从前后依赖的时许关系,我们不得不小心的处理回调,以防搞错层级。如果层级多了就有可能像这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
asyncOperation { asyncOperation1({ asyncOperation2({ asyncOperation3{ asyncOperation4{ asyncOperation5{ print("all executed") } } } }) }) } |
这就是传说中的callback hell
, 而且这还只是最clean的情况,实际情况中还会耦合很多的逻辑代码,更加无法维护。
用reduce来实现异步操作的串行
那是否有解决办法呢? 答案是有的。很多FRP的框架都提供了类似的实现,有兴趣的读者可以自行查看Promise、 ReactiveCocoa 和 RxSwift中的实现。
然后正如本节的标题所说,Swift提供了两个函数式的特性:
- 函数是一等公民(可以像变量一样传来传去,可以做函数参数、返回值
- 高阶函数,比如
map
和reduce
接下来我们就用这两个特性,实现一个更加优雅的方式来做异步操作串行。
1. 定义类型
为了方便书写,我们先定义一下异步操作的类型:
1 |
typealias AsyncFunc = (()->Void) -> Void |
AsyncFunc 代表了一个函数类型,这样的函数有一个闭包参数(其实就是上面 asyncOperation 的类型)
2. 从串行两个操作开始
我们先化简问题,假设我们只需要串行两个异步操作呢? 有没有办法把两个异步操作串行成一个异步操作呢? 想到这里,我们可以YY出这样一个函数:
1 2 3 |
func concat(left : AsyncFunc , right : AsyncFunc) -> AsyncFunc{ } |
concat函数,顾名思义,是连接的意思。指的是将两个异步操作:left
和right
串行起来,并返回一个新的异步操作。
那现在,我们来思考如何实现concat
函数,既然返回的是AsyncFunc
也就是一个函数,那我们可以先YY出这样的结构:
但其实用 Swift 本身自带很多Functional的特性,自己实现也并不难。
本文探讨了其中一种方法。
提起异步操作的序列执行,指的是有一系列的异步操作(比如网络请求)的执行有前后的依赖关系,前一个请求执行完毕后,才能执行下一个请求。
异步操作的定义
我们定义一般异步操作都是如下形式:
1 2 3 4 5 |
func asyncOperation(complete : ()-> Void){ //..do something complete() } |
常规的异步操作都会接受一个闭包作为参数,用于操作执行完毕后的回调。
那异步操作的序列化会有什么问题呢? 看如下的伪代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
func asyncOperation(complete : ()-> Void){ //..do something print("fst executed") complete() } func asyncOperation1(complete : ()-> Void){ //..do something print("snd executed") complete() } func asyncOperation2(complete : ()-> Void){ //..do something print("third executed") complete() } |
我们定义了三个操作asyncOperation
,asyncOperation1
和 asyncOperation2
,现在我们想序列执行三个操作,然后在执行完后输出 all executed
。 按照常规,我们就写下了如下的代码:
1 2 3 4 5 6 7 |
asyncOperation { asyncOperation1({ asyncOperation2({ print("all executed") }) }) } |
可以看到,明明才三层,代码似乎就有点复杂了,而我们真正关心的代码却只有 print("all executed")
这一行。但为了遵从前后依赖的时许关系,我们不得不小心的处理回调,以防搞错层级。如果层级多了就有可能像这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
asyncOperation { asyncOperation1({ asyncOperation2({ asyncOperation3{ asyncOperation4{ asyncOperation5{ print("all executed") } } } }) }) } |
这就是传说中的callback hell
, 而且这还只是最clean的情况,实际情况中还会耦合很多的逻辑代码,更加无法维护。
用reduce来实现异步操作的串行
那是否有解决办法呢? 答案是有的。很多FRP的框架都提供了类似的实现,有兴趣的读者可以自行查看Promise、 ReactiveCocoa 和 RxSwift中的实现。
然后正如本节的标题所说,Swift提供了两个函数式的特性:
- 函数是一等公民(可以像变量一样传来传去,可以做函数参数、返回值
- 高阶函数,比如
map
和reduce
接下来我们就用这两个特性,实现一个更加优雅的方式来做异步操作串行。
1. 定义类型
为了方便书写,我们先定义一下异步操作的类型:
1 |
typealias AsyncFunc = (()->Void) -> Void |
AsyncFunc 代表了一个函数类型,这样的函数有一个闭包参数(其实就是上面 asyncOperation 的类型)
2. 从串行两个操作开始
我们先化简问题,假设我们只需要串行两个异步操作呢? 有没有办法把两个异步操作串行成一个异步操作呢? 想到这里,我们可以YY出这样一个函数:
1 2 3 |
func concat(left : AsyncFunc , right : AsyncFunc) -> AsyncFunc{ } |
concat函数,顾名思义,是连接的意思。指的是将两个异步操作:left
和right
串行起来,并返回一个新的异步操作。
那现在,我们来思考如何实现concat
函数,既然返回的是AsyncFunc
也就是一个函数,那我们可以先YY出这样的结构: