Swift — 轉移到 Combine

讓我們一起來如何從原本的寫法轉移到 Combine 吧。

Jeremy Xue
Jeremy Xue ‘s Blog
9 min readAug 16, 2020

--

在 Combine 官方文件中有許多關於 Combine 的資料,而其中有一個區段是關於 Combine Migration,裡面的內容是關於如何將原有的編程方式轉移到 Combine 方式底下,而今天就讓我們來看看官方提供了哪些範例吧!

摘要:

  • 路由通知給 Combine 的訂閱者
  • 用計時器發布者替換基礎計時器
  • 使用 Combine 執行 Key-Value 觀察
  • 使用 Combine 用於應用中的異步程式碼

✒︎ 路由通知給 Combine 的訂閱者

透過使用通知中心(notification center)的發布者將通知傳遞給訂閱者。

許多框架都使用 NotificationCenter API 將異步事件傳遞到你的應用中。你的應用可能已經具有回調方法或閉包中接收和處理這些通知的位置。例如,以下程式碼使用 addObserver(forName:object:queue:using:) 在每次 iOS 設備旋轉到縱向時來印出一條訊息。

遷移通知處理程式碼來使用 Combine:

使用通知中心回調和閉包要求你完成在回調方法或閉包內所有工作。透過轉移到 Combine,你可以使用運算符來執行像是過濾之類的常見任務。

要利用 Combine,請使用 NotificationCenter.Publisher 將你的 NSNotification 處理程式碼轉移到 Combine 的慣用語法。你可以使用 NotificationCenter 的方法 publisher(for:object:) 來創建發布者,並傳入你感興趣的通知名稱以及源對象(如果有)。

如下表所示,在 Combine 中重寫上面的程式碼。此程式碼使用默認的通知中心來為 orientationDidChangeNotification 通知來創建發布者。當程式碼從此發布者接收到通知時,它應用過濾運算符來只對縱向通知進行操作,並印出一條訊息。

請注意,在這個情況下, orientationDidChangeNotification 在其 userInfo 字典中不包含新的方向,因此 filter(_:) 運算符直接查詢 UIDevice

✒︎ 用計時器發布者替換基礎計時器

使用計時器定期發布元素。

如果你的應用使用 Foundation 中的 Timer 類來重複接收回調或按指定間隔調用閉包,則可以將這些實例轉換為 Combine 來簡化你的程式碼。

使用定時器執行定期工作:

考慮以下程式碼片段,該程式碼使用 scheduledTimer(withTimeInterval:repeats:block:) 來在特定的執行緒上每秒更新一次數據模型的 lastUpdated 屬性:

轉換為計時器發布者:

要將此程式碼轉移到 Combine,請用 Timer.TimerPublisher 替換 scheduledTimer(withTimeInterval:repeats:block:) 返回的 Timer。你可以使用 Timer 方法 publish(every:tolerance:on:in:options:) 來創建此發布者。每次觸發底層 Timer 時,發布者都會發出一個新的日期,該日期代表被觸發的瞬間。然後,將 Combine 運算符應用於 Date,最終將發布者連結到訂閱者,例如 sink(receiveValue:)assign(to:on:)

Tip由於 Timer.TimerPublisher 遵循 ConnectablePublisher 協議,所以在你明確連接之前它不會產生任何元素。透過調用 connect() 或使用 autoconnect() 運算符在訂閱者連接時自動連接來執行操作。

下一個範例展示了如何使用 Timer.TimerPublisher 來替換上一個範例。它使用 Combine 的運算符來執行上一個範例中的閉包任務:

在此範例中,Combine 運算符替換了之前範例閉包中的所有行為:

  • receive(on:options:) 運算符確保其後續運算符在指定的執行緒上運行。這將替換之前的 async() 調用。
  • 透過使用 key path 設置 lastUpdate 屬性,assign(to:on:) 運算符更新數據模型。

使用 Combine 來簡化程式碼時,你會發現到的另一個優點是 Timer.TimerPublisher 會產生新的 Date 實例作為其輸出類型。第一個範例的閉包將 Timer 本身作為其參數,因此它必須手動創建新的 Date 實例。

✒︎ 使用 Combine 執行 Key-Value 觀察

使用 Combine 發布者暴露 KVO 更改。

許多框架使用 key-value 觀察來通知你的應用異步變更。透過將 KVO 的使用從回調和閉包轉換為 Combine,可以使你的程式碼更加優雅及可維護。

使用 KVO 監視變更:

在下列範例中,類型 UserInfolastLogin 屬性支持 KVO,如「在 Swift 中使用 Key-Value 觀察」中所述。viewDidLoad() 方法使用 observe(_:options:changeHandler:) 方法來設置一個用於處理任何對屬性變更的閉包。該閉包接收一個 NSKeyValueObservedChange 對象,取回 newValue 屬性並且印出它。viewDidAppear(_:) 方法變更該值,該值調用閉包並且印出訊息。

使用 Combine 來轉換 KVO 程式碼:

要將 KVO 程式碼轉換為 Combine,請使用 NSObject.KeyValueObservingPublisher 替換 observe(_:options:changeHandler:) 方法。你可以透過在父對象上調用 publisher(for:) 來獲取此發布者的實例,如以下範例的 viewDidLoad() 方法所示:

KVO 發布者產生觀察類型的元素(在此範例中為 Date),而不是 NSKeyValueObservedChange。這樣可以節省你一個步驟,因為你不必像第一個範例那樣從變更對象中解開 newValue

✒︎ 使用 Combine 用於應用中的異步程式碼

應用通用的模式來轉移基於閉包、事件處理程式碼。

你的應用可能使用通用的模式來處理異步事件,例如:

  • 完成處理(completion handler),在此處理程序中,調用者提供了一個閉包,在潛在的長時間運行的任務完成後執行一次。
  • 閉包屬性,調用者在其中提供閉包,以在每次給定的異步事件發生時調用。

Combine 為這些模式提供了有說服力的等價物,它使你可以消除樣板實現,並利用其眾多的運算符。當你在應用中其他部分採用 Combine 時,將異步調用點轉換為 Combine 可以提高程式碼的一致性和可讀性。

使用 Futures 來替換 Completion-Handler 閉包:

完成處理是函數所接收的閉包,該閉包在函數完成其工作後執行。通常,你可以透過在函數完成工作時直接調用 completion handler 實現此目的,並且在需要時將閉包存儲在函數外部。例如,下列的函數接收一個閉包,然後在兩秒的延遲後執行它:

你可以使用 Combine 的 Future 來替換此模式,發布者執行一些工作,然後異步發出成功或失敗的信號。如果成功,future 將執行 Future.Promise,這些一個接收 future 產生元素的閉包。你可以使用以下方式替換之前的函數:

Future 調用傳遞給他的 promise,並傳遞成功或失敗的 Result,而不是在工作完成時顯示調用閉包。調用者接收這個來自異步的結果。因為 Future 是 Combine 的 Publisher,所以調用方將其連接到可選的運算符鏈上,並以訂閱者做為結尾,例如 sink(receiveValue:)

使用 Output 類型來表示 Future 的參數:

有時候,長時間運算的任務會生成一個值,並將其作為參數傳遞給 completion handler。要在 Combine 中複製此功能,請將參數宣告為 future 發布的輸出類型。下面的範例產生一個隨機生成的整數,並且透過將 Int 宣告為 Future 的輸出類型,並將其傳遞給 promise

透過宣告 Future 產生 Int 元素,Future 可以使用 Result 類型將 Int 值傳遞給 promise。當 promise 執行時,Future 會發布該值,呼叫者可以使用訂閱者(像是 sink(receiveValue:))來接收該值:

使用 Subject 來替換重複調用的閉包:

你的應用還可能具有使用閉包來作為屬性來在某些事件發生時調用的常見模式。這些屬性的名稱通常以 on 開頭,其調用點如下所示:

使用 Combine,你可以透過使用 Subject 替換此模式。主題允許你透過調用 send() 方法在任何時候來強制發布新元素。透過使用 privatePassthroughSubjectCurrentValueSubject,然後將其暴露作為 AnyPublisher

使用這種安排,調用者可以在訂閱者中執行其工作,而不是設置一個閉包屬性,例如 sink(receiveValue:)

Combine 方法的另一個優點是 subject 可以調用 send(completion:) 來通知訂閱者沒有即將發生的其他事件,或者發生錯誤。

--

--

Jeremy Xue
Jeremy Xue ‘s Blog

Hi, I’m Jeremy. [好想工作室 — iOS Developer]