Swift — 使用 Combine 處理 URL 任務
讓我們看看如何使用 Combine 來處理常見的 URL 任務吧!
使用一系列的異步運算符來接收和處理來自 URL 的數據。
使用 URLSession
執行任務本質上是異步的,它從網路端點、文件系統和其他 URL 來源中獲取數據需要花費時間。URL 加載系統透過將結果異步傳遞給 delegate 或 completion handler 來解決此問題。Combine 框架還處理了異步性;使用它來處理你的 URL 任務結果可以簡化並授權你的程式碼。
創建數據任務發布者:
URLSession 提供一個 Combine 發布者 — URLSession.DataTaskPublisher
,該發布者從 URL
或 URLRequest
獲取數據的結果。你可以使用 dataTaskPublisher(for:)
來創建此發布者。任務完成後,它將發布以下任一內容:
- 如果任務成功,則包含獲取的數據和
URLResponse
的元組。 - 如果任務失敗,則是一個錯誤。
與傳遞給 dataTask(with:completionHandler:)
的 completion handler
不同,程式碼所接收的類型不是可選的,因為發布者已經解包數據或錯誤。
當使用 URLSession
基於 completion handler 的程式碼時,你必須在其閉包中完成所有工作:錯誤處理、數據解析等等。當你改用數據任務發布者時,可以將其中許多職責移動至 Combine 運算符中。
使用 Combine 運算符將原始數據轉換為你的類型:
當數據任務完成後,它將原始數據發送至你的應用中。大多數的應用需要將此數據轉換為自訂的類型。Combine 提供了運算符來執行這些轉換,允許你宣告一系列的處理運算。
數據任務發布者產生一個包含 Data
和 URLResponse
的元組。你可以使用 map(_:)
運算符將此元組的內容轉換為另一個類型。如果你想要在檢查數據之前檢查回應,使用 tryMap(_:)
並且在回應為不可接受時拋出錯誤。
要將原始數據轉換為自己符合 Decodable
協議的類型,請使用 Combine 的 decode(type:decoder:)
運算符。
以下範例結合了這兩個運算符來將 URL 端點中的 JSON 數據解析為自定義的 User
類型:
重試暫時性錯誤以及捕獲和替換持續性錯誤:
任何使用到網路的應用都應該預期會遭遇錯誤,並且你的應用應該能夠優雅的處理它們。由於暫時性的網路錯誤相當普遍,因此你可能需要立即重試失敗的數據任務。使用 URLSession
的 completion handler 慣用語法,你需要創建一個全新的任務來執行重試。使用數據任務發布者,你可以改用 Combine 的 retry(_:)
運算符。這透過重新創建上游發布者的訂閱指定次數來處理錯誤。但是,由於網路運算成本很高,因此只能重試幾次,並確保所有請求都是冪等(idempotent)的。
你還可以使用 Combine 運算符來替換錯誤,而不是讓錯誤傳達到訂閱者:
catch(_:)
:將錯誤替換為另一個發布者。你可以與其它URLSession.DataTaskPublisher
使用一起使用,例如從後備 URL 加載數據。replaceError(with:)
:用你提供的元素替換錯誤。如果在你的應用中有意義,則可以使用它來替代你期望從 URL 加載的值。
使用排程運算符在調度隊列之間移動工作:
當使用 URLSession
的 delegate 和 completion hanlder 慣用語法時,URLSession
將會在固定的委託隊列上回調你的程式碼。有時,這意味著你的回調程式碼必須手動使用調度隊列(Dispatch Queue)或其他調度 API 將工作放到特定隊列上。
使用 URLSesion.DataTaskPublisher
,你可以改用 Combine 的排程運算符(scheduling operators)。使用 receive(on:options:)
來指定你希望鏈中之後的運算符和訂閱者如何安排工作。DispatchQueue
和 RunLoop
兩者都實現了 Combine 的 Scheduler
協議,因此你可以使用它來們接收 URLSession
數據。以下程式碼片段確保 sink
將其結果紀錄在主調用隊列上:
與多個訂閱者共享數據任務發布者的結果:
你可能想在應用中的不同部分使用來自 URL 端點的數據。由於網路請求的成本很高,因此請勿不必要的重新發送。Combine 使你可以使用多個訂閱者訪問到單個 URLSession.DataTaskPublisher
,同時允許發布者透過單個請求為所有訂閱者提供服務。
要支持多個下游訂閱者,請使用 share()
運算符。此運算符的工作方式類似於 Publishers.Multicast
和 PassthroughSubject
發布者的組合。你可以將多個運算符鏈或訂閱者連接到 share()
運算符,任何上游發布者只能看到一個下游。對於 URLSession.DataTaskPublisher
,這意味著它只有執行一次數據任務。
以下範例使用 URLSession
數據任務用於兩個不相關的目的。一個訂閱者使用返回的數據來解析之前看過的自定義 User
類型,並將其結果記錄在主調度隊列中。第二個訂閱者只關心 URLResponse
,對其進行檢查來印出 HTTP 狀態碼,而不關心使用在哪個隊列。透過使用 share()
,數據任務發布者可以從 URL 端點單次加載就為兩個訂閱者提供服務。
為了證明此程式碼只有加載一次數據,請在 share()
運算符之前暫時放置一個 print(_:to:)
調試運算符。當該應用運行時,即使兩個訂閱者都收到了預期的結果,Xcode 的控制台輸出也顯示它僅從數據任務發布者那裡接收到了一個值。
請注意,當 URLSession.DataTaskPublisher
具有來自下游訂閱者未滿足的要求時 URLSession
就會開始加載數據 。在這種情況下,這將在第一個 sink
訂閱者連接時發生。如果你需要額外的時間來連接其他訂閱者,請使用 makeConnectable()
來將 Publishers.Share
發布者包裝為 ConnectablePublisher
。在連接所有訂閱者後,請在可連接的發布者上調用 connect()
來開始加載數據。