iOS如何优雅的处理“回调地狱Callback hell”(二)——使用Swift

前言

在上篇中,我谈到了能够用promise来解决Callback hell的问题,这篇咱们换一种方式同样能够解决这个问题。编程

咱们先分析一下为什么promise能解决多层回调嵌套的问题,通过上篇的分析,我总结也一下几点:swift

1.promise封装了全部异步操做,把异步操做封装成了一个“盒子”。 2.promise提供了Monad,then至关于flatMap。 3.promise的函数返回对象自己,因而就可造成链式调用promise

好了,既然这些能优雅的解决callback hell,那么咱们只要能作到这些,也同样能够完成任务。到这里你们可能就已经恍然大悟了,Swift就是完成这个任务的最佳语言!Swift支持函数式编程,分分钟就能够完成promise的基本功能。闭包

一.利用Swift特性处理回调Callback hell

咱们仍是以上篇的例子来举例,先来描述一下场景: 假设有这样一个提交按钮,当你点击以后,就会提交一次任务。当你点下按钮的那一刻,首先要先判断是否有权限提交,没有权限就弹出错误。有权限提交以后,还要请求一次,判断当前任务是否已经存在,若是存在,弹出错误。若是不存在,这个时候就能够安心提交任务了。app

那么代码以下:异步

func requestAsyncOperation(request : String , success : String -> Void , failure : NSError -> Void)
{
    WebRequestAPI.fetchDataAPI(request, success : { result in
        WebOtherRequestAPI.fetchOtherDataAPI ( result ,  success : {OtherResult in
            [self fulfillData:OtherResult];

            let finallyTheParams = self.transformResult(OtherResult)
            TaskAPI.fetchOtherDataAPI ( finallyTheParams , success : { TaskResult in

                let finallyTaskResult = self.transformTaskResult(TaskResult)

                success(finallyTaskResult)
                },
                failure:{ TaskError in
                    failure(TaskError)
                }

            )
            },failure : { ExistError in
                failure(ExistError)
            }
        )
        } , failure : { AuthorityError in
            failure(AuthorityError)
        }
    )
}复制代码

接下来咱们就来优雅的解决上述看上去很差维护的Callback hell。async

1.首先咱们要封装异步操做,把异步操做封装到Async中,顺带把返回值也一块儿封装成Result。 函数式编程

enum Result <T> {
    case Success(T)
    case Failure(ErrorType)
}

struct Async<T> {
    let trunk:(Result<T>->Void)->Void
    init(function:(Result<T>->Void)->Void) {
        trunk = function
    }
    func execute(callBack:Result<T>->Void) {
        trunk(callBack)
    }
}复制代码

2.封装Monad,提供Map和flatMap操做。顺带返回值也返回Async,以方便后面能够继续链式调用。 函数

// Monad
extension Async{


    func map<U>(f: T throws-> U) -> Async<U> {
        return flatMap{ .unit(try f($0)) }
    }

    func flatMap<U>(f:T throws-> Async<U>) -> Async<U> {
        return Async<U>{ cont in
            self.execute{
                switch $0.map(f){
                case .Success(let async):
                    async.execute(cont)
                case .Failure(let error):
                    cont(.Failure(error))
                }
            }
        }
    }
}复制代码

这是咱们把异步的过程就封装成一个盒子了,盒子里面有Map,flatMap操做,flatMap对应的其实就是promise的then性能

3.咱们能够把flatMap名字直接换成then,那么以前那30多行的代码就会简化成下面这样:

func requestAsyncOperation(request : String ) -> Async 
  
  
  

 
  
  { return fetchDataAPI(request) .then(fetchOtherDataAPI) .map(transformResult) .then(fetchOtherDataAPI) .map(transformTaskResult) } 

 复制代码

基本上和用promise同样的效果。这样就不用PromiseKit库,利用promise思想的精髓,优雅的完美的处理了回调地狱。这也得益于Swift语言的优势。

文章至此,虽然已经解决了问题了,不过尚未结束,咱们还能够继续再进一步讨论一些东西。

二.进一步的讨论

1.@noescape,throws,rethrows关键字 flatMap还有这种写法:

func flatMap<U> (@noescape f: T throws -> Async<U>)rethrows -> Async<U>复制代码

@noescape 从字面上看,就知道是“不会逃走”的意思,这个关键字专门用于修饰函数闭包这种参数类型的,当出现这个参数时,它表示该闭包不会跳出这个函数调用的生命期:即函数调用完以后,这个闭包的生命期也结束了。 在苹果官方文档上是这样写的:

A new @noescape attribute may be used on closure parameters to functions. This indicates that the parameter is only ever called (or passed as an @noescape parameter in a call), which means that it cannot outlive the lifetime of the call. This enables some minor performance optimizations, but more importantly disables the self. requirement in closure arguments.

那何时一个闭包参数会跳出函数的生命期呢?

引用唐巧大神的解释:

在函数实现内,将一个闭包用 dispatch_async 嵌套,这样这个闭包就会在另一个线程中存在,从而跳出了当前函数的生命期。这样作主要是能够帮助编译器作性能的优化。

throws关键字是表明该闭包可能会抛出异常。 rethrows关键字是表明这个闭包若是抛出异常,仅多是由于传递给它的闭包的调用致使了异常。

2.继续说说上面例子里面的Result,和Async同样,咱们也能够继续封装Result,也加上map和flatMap方法。

func ==<T:Equatable>(lhs:Result<T>, rhs:Result<T>) -> Bool{
    if case (.Success(let l), .Success(let r)) = (lhs, rhs){
        return l == r
    }
    return false
}

extension Result{

    func map<U>(f:T throws-> U) -> Result<U> {
        return flatMap{.unit(try f($0))}
    }

    func flatMap<U>(f:T throws-> Result<U>) -> Result<U> {
        switch self{
        case .Success(let value):
            do{
                return try f(value)
            }catch let e{
                return .Failure(e)
            }
        case .Failure(let e):
            return .Failure(e)
        }
    }
}复制代码

3.上面咱们已经把Async和Result封装了map方法,因此他们也能够叫作函子(Functor)。接下来能够继续封装,把他们都封装成适用函子(Applicative Functor)单子(Monad)

适用函子(Applicative Functor)根据定义: 对于任意一个函子F,若是能支持如下运算,该函子就是一个适用函子:

func pure<A>(value:A) ->F<A>

func <*><A,B>(f:F<A - > B>, x:F<A>) ->F<B>复制代码

以Async为例,咱们为它加上这两个方法

extension Async{

    static func unit(x:T) -> Async<T> {
        return Async{ $0(.Success(x)) }
    }

    func map<U>(f: T throws-> U) -> Async<U> {
        return flatMap{ .unit(try f($0)) }
    }

    func flatMap<U>(f:T throws-> Async<U>) -> Async<U> {
        return Async<U>{ cont in
            self.execute{
                switch $0.map(f){
                case .Success(let async):
                    async.execute(cont)
                case .Failure(let error):
                    cont(.Failure(error))
                }
            }
        }
    }

    func apply<U>(af:Async<T throws-> U>) -> Async<U> {
        return af.flatMap(map)
    }
}复制代码

unit和apply就是上面定义中的两个方法。接下来咱们在看看Monad的定义。

单子(Monad)根据定义: 对于任意一个类型构造体F定义了下面两个函数,它就是一个单子Monad:

func pure<A>(value:A) ->F<A>

func flatMap<A,B>(x:F<A>)->(A->F<B>)->F<B>复制代码

仍是以Async为例,此时的Async已经有了unit和flatMap知足定义了,这个时候,就能够说Async已是一个Monad了。

至此,咱们就把Async和Result都变成了适用函子(Applicative Functor)单子(Monad)了。

4.再说说运算符。 flatMap函数有时候会被定义为一个运算符>>=。因为它会将第一个参数的计算结果绑定到第二个参数的输入上面,这个运算符也会被称为“绑定(bind)”运算.

为了方便,那咱们就把上面的4个操做都定义成运算符吧。

func unit<T> (x:T) -> Async<T> {
    return Async{$0(.Success(x))}
}

func <^> <T, U> (f: T throws-> U, async: Async<T>) -> Async<U> {
    return async.map(f)
}

func >>= <T, U> (async:Async<T>, f:T throws-> Async<U>) -> Async<U> {
    return async.flatMap(f)
}

func <*> <T, U> (af: Async<T throws-> U>, async:Async<T>) -> Async<U> {
    return async.apply(af)
}复制代码

按照顺序,第二个对应的就是原来的map函数,第三个对应的就是原来的flatMap函数。

5.说到运算符,咱们这里还能够继续回到文章最开始的地方去讨论一下那段回调地狱的代码。上面咱们经过map和flatMap成功的展开了Callback hell,其实这里还有另一个方法能够解决问题,那就是用自定义运算符。这里咱们用不到适用函子的<*>,有些问题就可能用到它。仍是回到上述问题,这里咱们用Monad里面的运算符来解决回调地狱。

func requestAsyncOperation(request : String ) -> Async <String>
{
    return fetchDataAPI(request) >>= (fetchOtherDataAPI) <^>(transformResult) >>= (fetchOtherDataAPI) <^> (transformTaskResult)
}复制代码

经过运算符,最终原来的40多行代码变成了最后一行了!固然,咱们中间封装了一些操做。

三.总结

通过上篇和本篇的讨论,优雅的处理"回调地狱Callback hell"的方法有如下几种:
1.使用PromiseKit
2.使用Swift的map和flatMap封装异步操做(思想和promise差很少)
3.使用Swift自定义运算符展开回调嵌套

目前为止,我能想到的处理方法还有2种:
4.使用Reactive cocoa
5.使用RxSwift

下篇或者下下篇可能应该就是讨论RAC和RxSwift若是优雅的处理回调地狱了。若是你们还有什么其余方法能优雅的解决这个问题,也欢迎你们提出来,一块儿讨论,相互学习!

相关文章
相关标签/搜索