📦套件推坑:Swift Dependencies 超讚。它解決了哪些問題?

Swift Dependencies 是我最常使用的套件。它讓外部條件變成完全可控,解決了測試困難和 SwiftUI Preview 編譯緩慢的問題。使用它以後,我在區分元件職責、設計測試的功力都大幅提升。

歡迎來到「套件推坑:Swift Dependencies」系列的第一篇。從這篇文章開始,我會介紹 Swift Dependencies 這個我很愛用,且幾乎無法不使用的套件。

多年使用下來,我認為它是一個十分優秀的依賴注入套件,適合幾乎任何 iOS 專案,所以很想推坑給大家。

我會談為什麼要使用(它解決了哪些問題)、有哪些重要功能、什麼情況下使用、介紹底層實作的機制

Swift Dependencies 是由 Point-Free 團隊開發出的依賴注入的工具套件,它的語法參考了 SwiftUI 的 @Environment,並且大量運用於 TCA。不過,Swift Dependencies 可以完全獨立於 TCA 或 SwiftUI 使用。

每個實際運作的 iOS app 一定有無法控制的外部因素。舉例來說,提供地理定位服務的 app,需要使用者確認權限、裝置提供定位資訊。更簡單的例子如伺服器回傳的資料、系統時間、使用者偏好的語系、網路與藍牙連線狀況等等。

這些不可控制的因素,只能在 runtime 才能決定。開發者雖然可以事先寫好條件,但是直接讀取實際的值,很難進行測試,有些條件甚至需要重新安裝 app。反覆測試的話,非常沒有效率。

分離正式與測試環境,控制外部條件

開發者要根據條件進行不同的處理。而這些條件如果直接綁在設備或使用者的設定上,就完全無法撰寫測試,來確保各種分支的邏輯正確。

一個常見的情況,是向使用者要求某種隱私權限。根據使用者同意、不同意,要進行對應的流程。我們可以寫一個 LocationPermissionClient,把正式與測試的版本分開來實作。

正式的實作,就依照系統 API,呼叫 CLLocationManager 的方法。

測試時,我們可以在不同 test func,指定要求權限之後回傳的結果是同意、還是不同意,方便寫出對應的後續測試。

// Before: 無法測試的程式碼
if CLLocationManager.authorizationStatus() == .notDetermined {
    // 要怎麼測試這個分支?
}

// After: 完全可控
@Dependency(\.locationClient) var locationClient
if locationClient.authorizationStatus() == .notDetermined {
    // 測試時可以指定回傳值
}

如此一來,我們就不需要反覆手動刪除 app 重裝來重現測試條件,而是把所有外部條件都變成可控的

解決 Preview 編譯緩慢問題

許多開發者都困擾 SwiftUI 的 Preview 更新速度太慢,不堪使用。

不過,其實這往往是因為 SwiftUI 的模組同時編譯了太多不必要的外部依賴。透過合適的模組化(可參考🥇 SwiftUI + TCA 專案的模組化最佳實踐或是 iPlayground app 的程式碼),可以大幅改善 Preview 的效率。

其中關鍵就是在於把 SwiftUI 不需要編譯的外部依賴分隔開。我的工具策略就是利用今天的主角 Swift Dependencies