ソフトウェア設計

DIコンテナはいつ導入?Swinject・Factoryの導入時期まとめ

iOS開発をしていると、ある時こんな疑問が浮かびます。

読了 5 分
DIコンテナはいつ導入?Swinject・Factoryの導入時期まとめのカバー画像

iOS開発をしていると、ある時こんな疑問が浮かびます。

「これ、コンストラクタで渡せばいいのに、わざわざDIコンテナまで使う必要がある?」

サイドプロジェクトが大きくなると、誰もが一度は悩みます。私はSwinjectとFactoryを実際のプロジェクトで両方使い、判断基準を作りました。

結論からお話しします。

画面が20未満なら、DIコンテナなしでコンストラクタ注入だけで十分です。

依存関係グラフが複雑になり、テスト用モックの差し替えが頻繁になった時が導入のタイミングです。

導入するなら、選ぶべきはFactoryです。

いつ導入するのか、なぜFactoryなのかを、私の経験順に説明します。

DIコンテナ導入前に、まず確認したいこと

まず、混同しやすい点を整理します。

依存性注入(DI)とDIコンテナは別物です。

依存性注入は、必要なオブジェクトを外から渡す設計習慣です。ライブラリは一切必要ありません。

// これもすでに優れた依存性注入です
final class LoginViewModel {
    private let authService: AuthService
    init(authService: AuthService) {
        self.authService = authService
    }
}

一方、DIコンテナはオブジェクトをどこでどう作るかを一元管理するツールです。

コンストラクタ注入だけで足りるなら、コンテナはまだ早いでしょう。

導入のサインは次のように考えています。

  1. 初期化コードが画面ごとに繰り返され、オブジェクトを渡すのがつらくなった時
  2. テストで偽物のオブジェクトに差し替える機会が増えた時
  3. アプリ全体で同じインスタンス(シングルトン的なもの)を複数箇所で共有する必要がある時
  4. チームが大きくなり、「このオブジェクトはどこで作られるのか」と頻繁に尋ねるようになった時

二つ以上当てはまるなら、コンテナ導入を検討します。


SwinjectとFactory、何が違う?

Swinjectは2015年から使われてきたSwift界の伝統的なランタイムコンテナです。Containerに登録し、resolveで取り出します。問題は、このresolveがオプショナルを返すことです。登録を忘れてもコンパイルは通り、アプリ実行中にその画面でnilを強制アンラップした瞬間、クラッシュします。

Factoryは、こうした従来型コンテナの欠点を補うために生まれました。Resolverを作ったMichael Longが、その経験を基に再設計したものです。核心は「定義がそのまま登録になる」こと。コンテナのプロパティとしてファクトリを定義するため、登録漏れという概念がありません。誤った参照はランタイムではなくコンパイル時にエラーになります。

2026年時点で私が感じた違いを表にまとめます。

項目 Swinject Factory
登録・解決方式 ランタイムresolve(オプショナルを返す) 型ベースの定義+プロパティラッパー
登録漏れ時 ランタイムのnil/クラッシュ コンパイルエラーで事前に防止
スコープ管理 対応 対応(.singleton.cached.sharedなど)
外部依存性 別フレームワークへの依存 軽量な単一パッケージ
学習曲線 やや急 比較的なだらか

以前は「複雑なスコープ管理が必要ならSwinject」と言われましたが、今は違います。Factoryもシングルトン・キャッシュ・共有スコープをすべてサポートします。Swinjectが担っていたことを、Factoryはより安全に実現します。

実際に使うと、Factoryは1ファイルでも始められました
実際に使うと、Factoryは1ファイルでも始められました

だから結論はFactoryです

実際に判断した基準は次のとおりです。

新しく導入するならFactoryだけで十分です

個人プロジェクトでもチームプロジェクトでも、Factoryは外部依存性なしで1つのパッケージから始められるため、負担が少なかったです。何より、登録忘れによるランタイム障害が構造的になくなる点が決め手でした。

// Factory: 定義がそのまま登録です
extension Container {
    var authService: Factory<AuthService> {
        self { LiveAuthService() }.singleton  // スコープも1行で
    }
}
// 利用側
@Injected(\.authService) private var authService

テストでモックに差し替えるのも1行です。

Container.shared.authService.register { MockAuthService() }

Swinjectを残すのはいつ?

すでにSwinjectで構築された大規模なコードベースを保守する場合です。問題なく動くassembly構造を今すぐ取り除く理由はありません。ただし、そのようなプロジェクトでも新しいモジュールからFactoryへ段階的に移行するチームが増えています。新規導入でSwinjectを選ぶ理由は、今やほとんどありません。


よくある質問(Q&A)

Q. 最初からコンテナを入れて始めてもいい?

止めはしませんが、おすすめもしません。依存関係グラフが単純な時、コンテナはかえってコードを一層隠し、追跡を難しくします。

Q. SwinjectからFactoryへ移行できますか?

可能です。二つのコンテナを一時的に共存させ、モジュール単位で移す段階的な移行が現実的です。登録箇所とスコープ設定の見直しが必要なので大規模だと時間はかかりますが、移行後はランタイムクラッシュの心配がなくなります。

Q. SwiftUIプロジェクトにはどちらがよい?

断然Factoryです。プロパティラッパーを基盤とするためSwiftUIの宣言的なスタイルと自然に調和し、プレビューでモックを差し込むのも簡単です。


DIコンテナは必要になった時に導入するのが最もすっきりします。

導入すると決めたなら、迷わずFactoryを選びましょう。既存コンテナの欠点を補うために生まれたツールなので、小さく始めるにも大きく育てるにも十分です。先ほどの四つの導入サインに、今いくつ当てはまるか数えてみてください。