在上篇中,我談到了能夠用promise來解決Callback hell的問題,這篇咱們換一種方式同樣能夠解決這個問題。編程
咱們先分析一下爲什麼promise能解決多層回調嵌套的問題,通過上篇的分析,我總結也一下幾點:swift
1.promise封裝了全部異步操做,把異步操做封裝成了一個「盒子」。 2.promise提供了Monad,then至關於flatMap。 3.promise的函數返回對象自己,因而就可造成鏈式調用promise
好了,既然這些能優雅的解決callback hell,那麼咱們只要能作到這些,也同樣能夠完成任務。到這裏你們可能就已經恍然大悟了,Swift就是完成這個任務的最佳語言!Swift支持函數式編程,分分鐘就能夠完成promise的基本功能。閉包
咱們仍是以上篇的例子來舉例,先來描述一下場景: 假設有這樣一個提交按鈕,當你點擊以後,就會提交一次任務。當你點下按鈕的那一刻,首先要先判斷是否有權限提交,沒有權限就彈出錯誤。有權限提交以後,還要請求一次,判斷當前任務是否已經存在,若是存在,彈出錯誤。若是不存在,這個時候就能夠安心提交任務了。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若是優雅的處理回調地獄了。若是你們還有什麼其餘方法能優雅的解決這個問題,也歡迎你們提出來,一塊兒討論,相互學習!