iOS如何優(yōu)雅的處理“回調(diào)地獄Callback hell”(二)——使用Swift

前言

在上篇中,我談到了可以用promise來(lái)解決Callback hell的問(wèn)題,這篇我們換一種方式一樣可以解決這個(gè)問(wèn)題。

我們先分析一下為何promise能解決多層回調(diào)嵌套的問(wèn)題,經(jīng)過(guò)上篇的分析,我總結(jié)也一下幾點(diǎn):

1.promise封裝了所有異步操作,把異步操作封裝成了一個(gè)“盒子”。
2.promise提供了Monad,then相當(dāng)于flatMap。
3.promise的函數(shù)返回對(duì)象本身,于是就可形成鏈?zhǔn)秸{(diào)用

好了,既然這些能優(yōu)雅的解決callback hell,那么我們只要能做到這些,也一樣可以完成任務(wù)。到這里大家可能就已經(jīng)恍然大悟了,Swift就是完成這個(gè)任務(wù)的最佳語(yǔ)言!Swift支持函數(shù)式編程,分分鐘就可以完成promise的基本功能。

一.利用Swift特性處理回調(diào)Callback hell

我們還是以上篇的例子來(lái)舉例,先來(lái)描述一下場(chǎng)景:
假設(shè)有這樣一個(gè)提交按鈕,當(dāng)你點(diǎn)擊之后,就會(huì)提交一次任務(wù)。當(dāng)你點(diǎn)下按鈕的那一刻,首先要先判斷是否有權(quán)限提交,沒(méi)有權(quán)限就彈出錯(cuò)誤。有權(quán)限提交之后,還要請(qǐng)求一次,判斷當(dāng)前任務(wù)是否已經(jīng)存在,如果存在,彈出錯(cuò)誤。如果不存在,這個(gè)時(shí)候就可以安心提交任務(wù)了。

那么代碼如下:

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)
        }
    )
}

接下來(lái)我們就來(lái)優(yōu)雅的解決上述看上去不好維護(hù)的Callback hell。

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,以方便后面可以繼續(xù)鏈?zhǔn)秸{(diào)用。

// 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))
                }
            }
        }
    }
}

這是我們把異步的過(guò)程就封裝成一個(gè)盒子了,盒子里面有Map,flatMap操作,flatMap對(duì)應(yīng)的其實(shí)就是promise的then

3.我們可以把flatMap名字直接換成then,那么之前那30多行的代碼就會(huì)簡(jiǎn)化成下面這樣:


func requestAsyncOperation(request : String ) -> Async <String>
{
    return fetchDataAPI(request)
           .then(fetchOtherDataAPI)
           .map(transformResult)
           .then(fetchOtherDataAPI)
           .map(transformTaskResult)
}

基本上和用promise一樣的效果。這樣就不用PromiseKit庫(kù),利用promise思想的精髓,優(yōu)雅的完美的處理了回調(diào)地獄。這也得益于Swift語(yǔ)言的優(yōu)點(diǎn)。

文章至此,雖然已經(jīng)解決了問(wèn)題了,不過(guò)還沒(méi)有結(jié)束,我們還可以繼續(xù)再進(jìn)一步討論一些東西。

二.進(jìn)一步的討論

1.@noescape,throws,rethrows關(guān)鍵字
flatMap還有這種寫(xiě)法:

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

@noescape 從字面上看,就知道是“不會(huì)逃走”的意思,這個(gè)關(guān)鍵字專門(mén)用于修飾函數(shù)閉包這種參數(shù)類(lèi)型的,當(dāng)出現(xiàn)這個(gè)參數(shù)時(shí),它表示該閉包不會(huì)跳出這個(gè)函數(shù)調(diào)用的生命期:即函數(shù)調(diào)用完之后,這個(gè)閉包的生命期也結(jié)束了。
在蘋(píng)果官方文檔上是這樣寫(xiě)的:

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.

那什么時(shí)候一個(gè)閉包參數(shù)會(huì)跳出函數(shù)的生命期呢?

引用唐巧大神的解釋:

在函數(shù)實(shí)現(xiàn)內(nèi),將一個(gè)閉包用 dispatch_async
嵌套,這樣這個(gè)閉包就會(huì)在另外一個(gè)線程中存在,從而跳出了當(dāng)前函數(shù)的生命期。這樣做主要是可以幫助編譯器做性能的優(yōu)化。

throws關(guān)鍵字是代表該閉包可能會(huì)拋出異常。
rethrows關(guān)鍵字是代表這個(gè)閉包如果拋出異常,僅可能是因?yàn)閭鬟f給它的閉包的調(diào)用導(dǎo)致了異常。

2.繼續(xù)說(shuō)說(shuō)上面例子里面的Result,和Async一樣,我們也可以繼續(xù)封裝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.上面我們已經(jīng)把Async和Result封裝了map方法,所以他們也可以叫做函子(Functor)。接下來(lái)可以繼續(xù)封裝,把他們都封裝成適用函子(Applicative Functor)單子(Monad)

適用函子(Applicative Functor)根據(jù)定義:
對(duì)于任意一個(gè)函子F,如果能支持以下運(yùn)算,該函子就是一個(gè)適用函子:

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

func <*><A,B>(f:F<A - > B>, x:F<A>) ->F<B>

以Async為例,我們?yōu)樗由线@兩個(gè)方法


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就是上面定義中的兩個(gè)方法。接下來(lái)我們?cè)诳纯碝onad的定義。

單子(Monad)根據(jù)定義:
對(duì)于任意一個(gè)類(lèi)型構(gòu)造體F定義了下面兩個(gè)函數(shù),它就是一個(gè)單子Monad:

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

func flatMap<A,B>(x:F<A>)->(A->F<B>)->F<B>

還是以Async為例,此時(shí)的Async已經(jīng)有了unit和flatMap滿足定義了,這個(gè)時(shí)候,就可以說(shuō)Async已經(jīng)是一個(gè)Monad了。

至此,我們就把Async和Result都變成了適用函子(Applicative Functor)單子(Monad)了。

4.再說(shuō)說(shuō)運(yùn)算符。
flatMap函數(shù)有時(shí)候會(huì)被定義為一個(gè)運(yùn)算符>>=。由于它會(huì)將第一個(gè)參數(shù)的計(jì)算結(jié)果綁定到第二個(gè)參數(shù)的輸入上面,這個(gè)運(yùn)算符也會(huì)被稱為“綁定(bind)”運(yùn)算.

為了方便,那我們就把上面的4個(gè)操作都定義成運(yùn)算符吧。

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)
}

按照順序,第二個(gè)對(duì)應(yīng)的就是原來(lái)的map函數(shù),第三個(gè)對(duì)應(yīng)的就是原來(lái)的flatMap函數(shù)。

5.說(shuō)到運(yùn)算符,我們這里還可以繼續(xù)回到文章最開(kāi)始的地方去討論一下那段回調(diào)地獄的代碼。上面我們通過(guò)map和flatMap成功的展開(kāi)了Callback hell,其實(shí)這里還有另外一個(gè)方法可以解決問(wèn)題,那就是用自定義運(yùn)算符。這里我們用不到適用函子的<*>,有些問(wèn)題就可能用到它。還是回到上述問(wèn)題,這里我們用Monad里面的運(yùn)算符來(lái)解決回調(diào)地獄。


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

通過(guò)運(yùn)算符,最終原來(lái)的40多行代碼變成了最后一行了!當(dāng)然,我們中間封裝了一些操作。

三.總結(jié)

經(jīng)過(guò)上篇和本篇的討論,優(yōu)雅的處理"回調(diào)地獄Callback hell"的方法有以下幾種:
1.使用PromiseKit
2.使用Swift的map和flatMap封裝異步操作(思想和promise差不多)
3.使用Swift自定義運(yùn)算符展開(kāi)回調(diào)嵌套

目前為止,我能想到的處理方法還有2種:
4.使用Reactive cocoa
5.使用RxSwift

下篇或者下下篇可能應(yīng)該就是討論RAC和RxSwift如果優(yōu)雅的處理回調(diào)地獄了。如果大家還有什么其他方法能優(yōu)雅的解決這個(gè)問(wèn)題,也歡迎大家提出來(lái),一起討論,相互學(xué)習(xí)!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容