使用 Swift 開發 iOS App 時,一定會遇到一個令人煩惱的問題。
「相依性注入到底該採用哪種方式才對?」
先說結論。
除非有特殊理由,否則使用 建構式(init)注入,是最接近正解的選擇。
接下來會以實際經驗說明原因,以及其餘兩種方式適合在什麼時候使用。
三種注入方式,先掌握重點
在感到混亂之前,先把架構整理好。Swift 中注入相依性的方式大致有三種。
- 建構式(init)注入 —
init透過參數接收相依性 - 屬性注入 —
var稍後再指定給屬性 - 方法注入 — 透過獨立的方法注入
先看看最推薦的建構式注入程式碼。
final class OrderService {
private let repo: MemberRepository // let 確保不可變性
init(repo: MemberRepository) { // 建構式(init) 注入
self.repo = repo
}
}
不需要額外函式庫,只使用純 Swift 語法,因此程式碼很簡潔。
相較之下,屬性注入可以寫得這麼短。
final class OrderService {
var repo: MemberRepository! // 只有一行,看起來很方便,但…
}
一眼看去,屬性注入確實比較簡單。不過這份便利之後可能會造成問題。
為什麼不建議使用屬性注入?
先談談我親身遇到的情況。
屬性注入在測試時非常不方便。建立物件後若忘了注入,就會發生選用型別強制解包崩潰;光看型別也無法知道要填入什麼才算完成。
如果使用建構式注入,就能像 OrderService(repo: mockRepo) 一樣,直接用純 Swift 程式碼傳入 Mock 物件。
第二個問題是無法使用 let 關鍵字。
建構式注入可以將屬性宣告為 let,注入一次後就會成為不可變物件。
相反地,屬性注入是 var,值可以隨時變更,因此容易產生錯誤。
第三個問題是循環參照。
如果 A 參照 B,而 B 又參照 A,屬性注入要等 App 執行後才可能發現,必須進入該畫面才會崩潰。
建構式注入會在建立物件時立即暴露問題;若設計混亂,甚至會無法編譯,因此能更早修正問題。
一眼看懂的比較表
用文字說明容易混淆,所以整理成表格。(截至 2026 年 Swift 社群建議的方向)
| 分類 | 建構式(init)注入 | 屬性注入 | 方法注入 |
|---|---|---|---|
| 不可變性(let) | 可以 ⭕ | 不可以 ❌ | 不可以 ❌ |
| 測試便利性 | 高 | 低 | 普通 |
| 循環參照偵測 | 建立時立即偵測 | 較晚發現 | 較晚發現 |
| 必要/選用相依性 | 適合必要相依性 | 難以區分 | 適合選用相依性 |
| 程式碼簡潔度 | 普通 | 非常簡潔 | 普通 |
只看表格也能看出整體方向了吧?
建構式注入在大多數項目中都勝出。因此,Swinject 或 Factory 等 DI 函式庫的文件也都將建構式注入列為預設建議。
那麼其餘兩種方式該在什麼時候使用?
這不是說任何情況都只能使用建構式注入。每種方式都有適合自己的場景。
方法注入很適合選用相依性。
當注入對象可以不存在,也就是有就使用、沒有也沒關係的相依性,可以透過 configure(with:) 這類方法彈性地注入。
屬性注入若要使用,適合用在無法自行控制初始化時機的地方,例如透過 Storyboard 建立的視圖控制器。
在一般應用程式碼中,最好盡量避免使用。
總結如下。
- 必要相依性 → 建構式注入
- 選用相依性 → 方法注入
- 一般程式碼中的屬性注入 → 盡量避免
常見問題
Q. 使用 Factory 這類 DI 函式庫後,建構式注入會更方便嗎?
會。在 Swinject 或 Factory 中註冊相依性的建立方式後,容器會代替你建立要傳給建構式的物件。程式碼變短,同時仍能保有 let不可變性等優點。
Q. 如果建構式的參數變得太多呢?
這不是注入方式的問題,而是該類別負責太多工作的訊號。請先考慮拆分責任並分離類別。
Q. 小型專案也一定需要 DI 函式庫嗎?
不需要。如果只有幾個畫面,只要使用 init 直接傳入相依性的純建構式注入就足夠了。
一開始很容易被屬性注入的便利性吸引,但實際撰寫測試並與他人協作後,就會親身感受到為什麼大家如此強調建構式注入的優點。
如果不知道該怎麼選,先從建構式注入開始吧。程式碼越來越大時,你會慶幸做了這個選擇。

