?
本文首發(fā)于 Ficow Shen's Blog,原文地址: Combine 框架,從0到1 —— 4.在 Combine 中執(zhí)行異步代碼。
?
內(nèi)容概覽
- 前言
- 用
Future取代回調(diào)閉包 - 用輸出類型(
Output Types)代表Future的參數(shù) - 用
Subject取代重復執(zhí)行的閉包 - 總結(jié)
?
前言
?
你的應(yīng)用可能會使用一些常見的模式來處理異步事件,比如:
- 完成處理器(Completion handlers)。它其實是調(diào)用方提供的一個閉包,當一個耗時任務(wù)完成后,這個閉包會被調(diào)用一次;
- 閉包屬性(Closure properties)。它其實也是調(diào)用方提供的一個閉包,這個閉包會在每一次異步事件發(fā)生時被調(diào)用;
Combine 為這些模式提供了強大的替換選項。它可以讓你消除這種樣板代碼,并且充分利用 Combine 中的操作符。當你在應(yīng)用的其他地方采用 Combine 時,將異步調(diào)用點轉(zhuǎn)換為 Combine 可以提高代碼的一致性和可讀性。
?
用 Future 取代回調(diào)閉包
?
一個完成處理器其實就是一個傳給某個方法的閉包參數(shù),當這個方法完成任務(wù)時,它就會調(diào)用這個閉包。
比如下面的代碼,這個函數(shù)接收一個閉包,然后在延時2秒之后執(zhí)行這個閉包:
func performAsyncAction(completionHandler: @escaping () -> Void) {
DispatchQueue.main.asyncAfter(deadline:.now() + 2) {
completionHandler()
}
}
你可以用 Combine 中的 Future 去替換這種模式的代碼。用 Future 創(chuàng)建一個發(fā)布者去執(zhí)行一些工作,然后異步發(fā)送成功或者失敗的信號。如果執(zhí)行成功,Future 就會執(zhí)行一個 Future.Promise,這是一個用來接收 Future 生成的元素的閉包。
你可以像這樣重寫上面的代碼:
func performAsyncActionAsFuture() -> Future <Void, Never> {
return Future() { promise in
DispatchQueue.main.asyncAfter(deadline:.now() + 2) {
promise(Result.success(()))
}
}
}
當任務(wù)完成時,Future 會調(diào)用傳入的 promise, 并把代表執(zhí)行成功或者失敗的結(jié)果 Result 傳給 promise,而不是像之前的代碼那樣調(diào)用一個顯式傳入的閉包。然后,調(diào)用方會通過 Future 異步接收這個結(jié)果。由于 Future 是一個 Combine 發(fā)布者,調(diào)用方會把自己連接到一個可選的操作符鏈上,鏈的尾部是一個訂閱者,比如:sink(receiveValue:)。
調(diào)用方的代碼如下所示:
cancellable = performAsyncActionAsFuture()
.sink() { _ in print("Future succeeded.") }
?
用輸出類型(Output Types)代表 Future 的參數(shù)
?
有時,一個耗時任務(wù)生成的結(jié)果需要作為一個參數(shù)傳遞給完成處理器閉包。要在 Combine 中實現(xiàn)同樣的功能,就需要為這個參數(shù)聲明 Future 發(fā)布的輸出類型。
下方的示例可以生成一個隨機整型數(shù)。它將 Future 發(fā)布的輸出類型聲明為 Int類型, 并將生成的整型值傳遞給 promise:
func performAsyncActionAsFutureWithParameter() -> Future <Int, Never> {
return Future() { promise in
DispatchQueue.main.asyncAfter(deadline:.now() + 2) {
let rn = Int.random(in: 1...10)
promise(Result.success(rn))
}
}
}
?
請注意,
performAsyncActionAsFuture方法的返回值是Future <Void, Never>類型,而performAsyncActionAsFutureWithParameter方法的返回值是Future <Int, Never>類型。
?
通過聲明 Future 產(chǎn)生的元素為 Int 類型,Future 可以使用 Result 給 promise 傳入整型值。當 promise 執(zhí)行結(jié)束時,Future 會發(fā)布這個值,然后調(diào)用方就可以通過訂閱者(如:sink(receiveValue:))接收到這個值:
cancellable = performAsyncActionAsFutureWithParameter()
.sink() { rn in print("Got random number \(rn).") }
?
用 Subject 取代重復執(zhí)行的閉包
?
你的應(yīng)用也可能會采用這種常見的模式:將一個閉包作為一個屬性,然后當某個事件發(fā)生時執(zhí)行這個閉包屬性。這種屬性的命名通常以 on 開頭,然后調(diào)用點看起來就像這樣:
vc.onDoSomething = { print("Did something.") }
有了 Combine,你可以使用 Subject 替代這種模式。一個 Subject 實例允許你在任何時候通過調(diào)用 send() 方法來命令式地發(fā)布一個新元素。你可以通過使用私有的 PassthroughSubject 或者 CurrentValueSubject 來采用這種模式,然后將其作為一個 AnyPublisher 暴露給外部以便外部調(diào)用方使用它:
private lazy var myDoSomethingSubject = PassthroughSubject<Void, Never>()
lazy var doSomethingSubject = myDoSomethingSubject.eraseToAnyPublisher()
// 發(fā)布一個空元組元素
myDoSomethingSubject.send(())
然后,調(diào)用方只需要在訂閱者中執(zhí)行對應(yīng)的操作即可,不需要配置一個閉包屬性:
cancellable = vc.doSomethingSubject
.sink() { print("Did something with Combine.") }
這種基于 Combine 的方法還有一個優(yōu)勢,subject 可以調(diào)用 send(completion:) 來告知訂閱者:不會再有后續(xù)的事件發(fā)生,或者發(fā)生了一個錯誤。
?
總結(jié)
?
通過學習上述內(nèi)容,我們可以感覺到:遷移現(xiàn)有的異步代碼到 Combine 是比較容易的。而且,因為 Combine 提供了很多常用的操作符,它可以極大地提升我們的開發(fā)效率!
可以想象一下,以前寫的很多方法/函數(shù),現(xiàn)在只需要使用 Combine 就可以寫成非常易讀而且優(yōu)雅的鏈式調(diào)用代碼。如此一來,使用 Swift 進行開發(fā)的體驗又會提升不少呢!
朋友,行動起來吧!把現(xiàn)有項目中的舊代碼重構(gòu)成使用 Combine 的代碼~
?
推薦繼續(xù)閱讀:Combine 框架,從0到1 —— 5.Combine 提供的發(fā)布者(Publishers)
?
本文內(nèi)容來源:
Using Combine for Your App's Asynchronous Code
?