ソフトウェア設計

Swift DIライブラリFactoryまとめ:Swinjectとの違いは?

Factoryは依存性の登録漏れをコンパイル時に検出するSwift DIライブラリです。2026年7月時点の3.3.1を基準に、基本的な使い方、スコープ宣言、テスト・プレビューへの注入、Swinjectとの違いをまとめます。

読了 6 分
Swift DIライブラリFactoryまとめ:Swinjectとの違いは?のカバー画像

前回はSOLID(オブジェクト指向における5つの設計原則)の最後の文字、DIP(依存性逆転の原則)を解説しました。原則を理解すると、次の疑問が自然に浮かびます。「では、これらの依存性を実際のアプリで誰が、どう組み立てるのか?」

小規模なプロジェクトなら、イニシャライザに直接渡す手動DI(Dependency Injection、依存性注入)で十分です。しかし画面が数十個に増え、依存関係グラフが深くなると、組み立てコード自体が負担になります。今回は、そうした場面で使いやすいSwift DIライブラリ、Factoryを紹介します。2026年7月時点の最新版3.3.1を基準にしています。


手動DIはどこまで耐えられるか

DIPを守るコードは、おおむね次のような形でした。

protocol NetworkProviding {
    func fetch(_ url: URL) async throws -> Data
}

final class OrderViewModel {
    private let network: NetworkProviding

    init(network: NetworkProviding) {
        self.network = network
    }
}

プロトコルに依存し、実装は外部から注入します。ここまではライブラリ不要です。問題は組み立てる側にあります。

// どこかの組み立てポイント(Composition Root)
let network = NetworkProvider()
let repository = OrderRepository(network: network)
let analytics = AnalyticsService(network: network)
let viewModel = OrderViewModel(repository: repository, analytics: analytics)

依存性が3層、4層と深くなると、このような初期化コードが画面ごとに繰り返されます。中間層に依存性を1つ追加すると、それを経由するすべての初期化コードを修正しなければなりません。シングルトンに逃げたくなる瞬間ですが、シングルトンがテストをどう妨げるかはご存じでしょう。DIコンテナは、この組み立ての問題を引き受けるツールです。

Factoryの違い:コンパイル時の安全性

Swift DIライブラリといえば、長い間Swinjectが標準のように使われてきました。Swinjectは文字列・型ベースで登録し、resolve()で取り出す構成なので、登録漏れがあってもコンパイルは通り、実行時にnilが発生します。アプリを実行して初めてミスに気づくことになります。

Factoryはこの点を反転させました。依存性をContainerの計算プロパティとして定義するため、存在しない依存性を参照するとコンパイルできません。タイプミスも登録漏れもビルド時に検出されます。

さらに、実行コードが1,000行未満の軽量ライブラリであり、コンパイル時コード生成スクリプトなしに純粋なSwiftだけで動作する点も利点です。Needleのようにビルドパイプラインへツールを組み込む必要がありません。

Factoryの基本的な使い方を3分で整理

SPM(Swift Package Manager)でインストールします。パッケージURLはhttps://github.com/hmlongco/Factory、3.xからのインポートはFactoryKitです。

依存性は、Containerのエクステンションに計算プロパティを追加して登録します。

import FactoryKit

extension Container {
    var networkService: Factory<NetworkProviding> {
        self { NetworkProvider() }
    }
    var orderRepository: Factory<OrderRepositoryType> {
        self { OrderRepository(network: self.networkService()) }
    }
}

取り出し方は3つあります。

// 1. プロパティラッパー注入
final class OrderViewModel {
    @Injected(\.orderRepository) private var repository
}

// 2. 直接呼び出し
let repository = Container.shared.orderRepository()

// 3. コンストラクタ注入 — 組み立てだけをコンテナに任せる
extension Container {
    var orderViewModel: Factory<OrderViewModel> {
        self { OrderViewModel(repository: self.orderRepository()) }
    }
}

3番目の方法は注目に値します。クラス自体はライブラリをまったく知らない純粋なコンストラクタ注入の形を保ち、組み立てコードだけをコンテナに任せられるからです。DIP編で扱った「実装の組み立ては外側の責務」という原則に正確に合致します。

SwiftUIでは@InjectedObservableを使ってObservableなビューモデルを直接受け取れます。

struct OrderView: View {
    @InjectedObservable(\.orderViewModel) var viewModel
}
コンテナがベルトコンベアで依存性モジュールをアプリ画面に組み立てて届けるイラスト
登録部に宣言しておけば、組み立てはコンテナの役目です

スコープ:インスタンスの寿命を宣言する

DIコンテナを使うもう1つの理由は、インスタンスの寿命を管理できることです。Factoryでは登録部に修飾子を1つ付けるだけです。

extension Container {
    var networkService: Factory<NetworkProviding> {
        self { NetworkProvider() }.singleton
    }
    var imageCache: Factory<ImageCaching> {
        self { ImageCache() }.cached
    }
}
  • unique — デフォルト値。要求されるたびに新しいインスタンスを作ります。
  • singleton — アプリ全体で1つのインスタンスを共有します。
  • cached — キャッシュをリセットするまで同じインスタンスを返します。
  • shared — 誰かが強参照を保持している間だけ維持され、誰も使わなくなると解放されます。

グローバルなシングルトンオブジェクトを直接作る場合との違いは、寿命がコード各所に散らばったstatic letではなく、登録部の1か所に宣言として集約される点です。後からsingletonをcachedに変えたい場合も、修飾子を1つ変えるだけで済みます。

テストとプレビューで真価を発揮する

DIを導入する最も実用的な理由は、結局テストです。Factoryでは登録をその場で上書きできます。

import FactoryTesting

@Suite(.container)  // テストごとにコンテナを分離
struct OrderViewModelTests {
    @Test func loadsOrders() async {
        Container.shared.orderRepository { MockOrderRepository() }
        let viewModel = Container.shared.orderViewModel()
        await viewModel.load()
        #expect(viewModel.orders.count == 3)
    }
}

Swift Testingでは、FactoryTestingターゲットに.containerトレイトを付けると、テスト間でコンテナの状態が混ざりません。SwiftUIのプレビューでも同じ方法でモックを差し込めます。

#Preview {
    Container.shared.orderRepository { MockOrderRepository() }
    return OrderView()
}

特定の実行環境でだけ自動的に差し替えるコンテキスト修飾子もあります。デバッグビルドでだけスタブのアナリティクスを使いたい場合は、次のようにします。

container.analytics.onDebug { StubAnalyticsEngine() }

同じ要領で、テスト、プレビュー、シミュレータ環境ごとのオーバーライドを登録部に宣言できます。

機械スロットの実モジュールをモックモジュールに交換するテスト注入のイラスト
テストでは本物の実装の代わりにモックを差し込みます

Factory 3.xで変わったこと

2.xから移行する方に向けて、主な変更点を整理します。

  • インポート名の変更import Factoryimport FactoryKitに変わりました。移行の大半はこの置換です。
  • Swift 6 Strict Concurrencyを完全サポート@MainActorビューモデルを登録する際、2.xではクロージャ内に@MainActor inを重複して書く必要がありましたが、3.xではファクトリ宣言部のアノテーションだけで処理できます。
  • SPM専用 — CocoaPodsのサポートは終了しました。CocoaPodsプロジェクトではFactory 2.5.3に留まるか、ソースを直接埋め込む必要があります。
  • Swift Testing対応 — 上で見た.containerトレイトを含め、テスト分離のサポートが正式に追加されました。
Containerの登録部をアプリコード・テスト・プレビューで共有するFactoryの構成図
アプリコード・テスト・プレビューが1つの登録部を共有します

まとめ

DIPが「具体ではなく抽象に依存せよ」という方向を示すなら、Factoryはその方向を実際のコードベースで維持するコストを下げるツールです。コンパイル時の安全性により登録漏れはビルドエラーになり、スコープやモックの差し替えも数行の宣言で済みます。

Swinjectで問題なく動いているプロジェクトまで作り直す必要はありません。ただ、新規プロジェクトならFactoryを標準の選択肢として始めてもよいでしょう。軽量で導入の負担が小さく、気に入らなければコンストラクタ注入の構造を保ったままコンテナだけ取り除けます。

手動DIで耐えてきたものの、組み立てコードが足かせになり始めたなら、一度試してみてください。


参考資料

あわせて読みたい