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