本文的目的是為了總結(jié)下我在開發(fā)者大會上看傅若愚做的演講。因為大會的時間比較短,所以,他并沒有足夠的時間來解釋清楚他是如何解決回調(diào)地獄的。我后續(xù)請教了他,他用下面非常清晰思維過程做了回復!
再此,非常感謝@傅若愚的耐心指導,大家有空也一定要看一看@swift開發(fā)者大會的相關視頻,一定會收貨頗多。
以下內(nèi)容基本出自他的講義,以及他對我提的問提“為什么使用這種方式解決回調(diào)地獄的思考過程”的回復。


抽象這個過程
Hmmmm,為什么我們會想到用Async來簡化回調(diào)地獄呢?我們得回頭想想所謂的回調(diào)地獄到底是怎么一回事,首先一起來看一段相對簡單的“回調(diào)地獄”吧(為了簡單起見,我們先不考慮錯誤處理):
getFirstItem() {
firstItem in getSecondItem(firstItem) {
secondItem in getThirdItem(secondItem){
thirdItem in
//Here we get our third item
}
}
}
所以它到底是在干什么?如果我們將它按照人類的語言描述一遍,并盡量不遺漏掉任何的代碼執(zhí)行過程:
1、異步地發(fā)出一個請求,在回調(diào)中獲得請求的數(shù)據(jù)。(即firstItem)然后根據(jù)上一步的數(shù)據(jù),在getSecondItem中計算出一個新的請求。
2、異步地發(fā)出這個新的請求,并在回調(diào)中獲得請求的數(shù)據(jù)。(即secondItem)然后根據(jù)上一步的數(shù)據(jù),在getThirdItem中計算出一個新的請求。
3、異步地發(fā)出這個新的請求,并在回調(diào)中獲得請求的數(shù)據(jù)。(即thirdItem)
分析這個過程
于是我們可以看到,所謂的“回調(diào)地獄”其實也不過是在重復地做一些計算的過程,如果我們能夠找到一種對類似的計算過程的封裝方法,那么我們就能解決“回調(diào)地獄”的問題。
這里如果直接去想,去找,真的是挺難的,原因在于我們平時寫代碼其實更多地是在對數(shù)據(jù)進行抽象:比如我們會去抽象一個用戶,一個商品——這些東西都是一個一個的對象(或者說封裝之后的數(shù)據(jù)),即便涉及到對象的方法,那本質(zhì)上也是在對數(shù)據(jù)的行為進行的抽象。而現(xiàn)在,我們卻是需要對一種計算的過程進行抽象,它需要的編程思維和我們平時所習慣的代碼寫法完全是不一樣的!
想想是否有現(xiàn)有的模式去解決這個問題
那么,有沒有類似的已經(jīng)做出來的其他過程抽象的例子可以給我們參考,幫助我們進行思考呢?答案當然是有的,并且不止一個!如果對Swift的標準庫比較熟悉,那么很容易想到Optional.flatMap這個函數(shù):
public enum Optional<Wrapped> : _Reflectable, NilLiteralConvertible {
case None
case Some(Wrapped)
/// Construct a `nil` instance.
public init()
/// Construct a non-`nil` instance that stores `some`.
public init(_ some: Wrapped)
/// If `self == nil`, returns `nil`. Otherwise, returns `f(self!)`.
@warn_unused_result
public func map<U>(@noescape f: (Wrapped) throws -> U) rethrows -> U?
/// Returns `nil` if `self` is nil, `f(self!)` otherwise.
@warn_unused_result
public func flatMap<U>(@noescape f: (Wrapped) throws -> U?) rethrows -> U?
/// Create an instance initialized with `nil`.
public init(nilLiteral: ())
}
這個函數(shù)的具體用法如下:
someOptionalNumber
.flatMap(divide3) //{x in x == 0 ? nil : return 3/x}
.flatMap(divide5) //{x in x == 0 ? nil : return 5/x}
//我們還可以無限地鏈下去......
如果我們把上面這段代碼像之前那樣描述一遍:
1、對一個Optional數(shù)據(jù),確保這個數(shù)據(jù)的值不為nil,否則后面的步湊不執(zhí)行。然后根據(jù)上一步的數(shù)據(jù),在divide3中計算出一個新的Optional數(shù)據(jù)。
2、對這個新的Optional數(shù)據(jù),確保這個數(shù)據(jù)的值不為nil,否則后面的步湊不執(zhí)行然后根據(jù)上一步的數(shù)據(jù),在divide5中計算出一個新的Optional數(shù)據(jù)。
3、對這個新的Optional數(shù)據(jù)……
終于找到了這個計算過程
我們發(fā)現(xiàn),這個過程竟然與我們此前的描述很多部分驚人的相似!
然后根據(jù)上一步的數(shù)據(jù),在XXX中計算出一個新的XXXX。
這句話似乎應該是一個pattern,如果它是一個pattern,肯定不止會在一個地方出現(xiàn)!于是我們再來看看這個我們可能更加熟悉的函數(shù)!
[1,2,3,4,5]
.flatMap(duplicate) // { x in return [x, x] }
.flatMap(anotherFunction) // {x in return [x, x]}
//[1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5]
老辦法,把它真正做的事情翻譯一遍,并盡量不遺漏任何計算過程:
遍歷一個Array,得到其中的每一個數(shù)據(jù)然后根據(jù)上一步的數(shù)據(jù),在duplicate中計算出一個新的Array
遍歷這個新的Array,得到其中的每一個數(shù)據(jù)然后根據(jù)上一步的數(shù)據(jù),在anotherFunction中計算出一個新的Array
......
果然出現(xiàn)了!如果我們現(xiàn)在再根據(jù)上下文回去看我們“回調(diào)地獄”的問題,我們可以確信,只要我們將這個步驟封裝好了,我們的問題也就解決了!并且我們所需要做的事情僅僅是定義一個針對回調(diào)函數(shù)的flatMap而已。首先,讓我們把一個異步函數(shù)封裝進一個結(jié)構(gòu)體,什么是異步函數(shù)呢?比如:
func async(callback: String->Void){}
這就是一個異步函數(shù),它的簽名是 (String->Void)->Void的形式。那么我們首先將形如
(T->Void)->Void的函數(shù)封裝起來:
struct Async<T>{ let asyncFunction:(T->Void)->Void}
接下來,我們觀察Optional和Array的flatMap函數(shù):
//Optional 的 flatMap函數(shù)定義
/// Returns `nil` if `self` is nil, `f(self!)` otherwise.
@warn_unused_result
public func flatMap<U>(@noescape f: (Wrapped) throws -> U?) rethrows -> U?
如果不知道@noescape,throws,rethrows,去掉無妨閱讀,比如,這樣理解
public func flatMap<U>( f: (Wrapped) throws -> U?) -> U?
是不是更加清晰了
//Array 的 flatMap函數(shù)定義
extension SequenceType {
/// Return an `Array` containing the non-nil results of mapping
/// `transform` over `self`.
///
/// - Complexity: O(*M* + *N*), where *M* is the length of `self`
/// and *N* is the length of the result.
@warn_unused_result
public func flatMap<T>(@noescape transform: (Self.Generator.Element) throws -> T?) rethrows -> [T]
}
同理:去掉@noescape等等干擾
public func flatMap<T>(transform: (item) -> T?) -> [T]
他們的函數(shù)簽名幾乎一模一樣:
flatMap<U>(transform:T -> M<U>) -> M<U> //其中M<U>表示Array<U>或者Optional<U>
那么,我們的flatMap也應該是這個樣子!
struct Async<T>{
let asyncFunction:(T->Void)->Void
func flatMap<U>(transform: T->Async<U>)->Async<U>{
//fill this function
}}
剩下的事情,就只是將這個函數(shù)體按照前面整理出的“然后根據(jù)上一步的數(shù)據(jù),在transform中計算出新的Async,進行補玩就OK了。
具體補完的代碼我就不在這里寫出來了,如果實在還寫不出,可以去參考傅若愚GitHub上面找一個Demo,但我更建議你嘗試著自己將它補完。
------最后,誰提Monad我跟誰急!------ 這個是@傅若愚說的,monad只是一個概念而已,他的意思應該是指,我們不要丟下更加重要的抽象計算過程不顧,而去追逐一個對函數(shù)式編程思想不太重要的概念。