Combine 框架,從0到1 —— 4.在 Combine 中執行非同步程式碼

Ficow發表於2020-09-20

 

本文首發於 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 可以使用 Resultpromise 傳入整型值。當 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

 

相關文章