【iOS/Xcode/Swift】SwinjectでDI(依存性注入)を実装する方法と使い方
この記事からわかること
- iOS/XcodeでDI(依存性注入)の実装方法
- Swinjectの使い方
index
[open]
\ アプリをリリースしました /
環境
- Xcode:16.3
- iOS:18.4
- Swift:6
- Swinject:2.9.1
- macOS:Sequoia 15.4
DI(Dependency Injection:依存性注入)とは?
「DI(Dependency Injection)」は「依存関係をオブジェクト外に持たせて外部から注入するような設計にする」デザインパターンの1つです。日本語では依存性注入という意味になります。詳細は以下の記事を参考にしてください。
Swinjectとは?
「Swinject」はSwift向けの軽量な依存性注入(DI)ライブラリです。類似のライブラリとしてStoryboard向けの依存性注入(DI)ライブラリSwinjectStoryboardも用意されていますので活用してみてください。
ライブラリの導入
SwinjectはCocoa Pods、Swift Package Manager、Carthageをサポートしているのでよしななものを使用して導入してください。
pod 'Swinject'
Swift Package Managerなら以下で検索してください。
https://github.com/Swinject/Swinject.git
Swinjectの使用例
依存性注入を行う主な方法は2つありますがSwinjectでは主にコンストラクタインジェクションを使って行きます。。
- コンストラクタインジェクション
- フィールドインジェクション(セッターインジェクション)
コンストラクタインジェクション
コンストラクタインジェクションはその名前の通りコンストラクタを使用して依存性を挿入する方法です。ViewModelやRepositoryなどシンプルな独自のクラスなどでは簡単にコンストラクタインジェクションを実装することができます。
フィールドインジェクション(セッターインジェクション)
AndroidでのFragmentやActivityなどシステムによってインスタンス化されるクラスはコンストラクタインジェクションが不可能です。そのためクラスの作成後に依存関係がインスタンス化されます。
DIコンテナを作成する
SwinjectではDIコンテナと呼ばれるものをオブジェクトを使用して依存性注入を管理しています。DIコンテナはContainerクラスとして管理されています。このクラスに対してregisterメソッドを使用して依存元と依存先をマッピングを定義します。
import Swinject
final class DIContainer: @unchecked Sendable{
static let shared = DIContainer()
private let container = Container() { c in
// DIコンテナに依存元と依存先をマッピングして定義する
c.register(Repository.self) { _ in RepositoryImpl() }
c.register(ViewModel.self) { r in
ViewModel(repository: r.resolve(Repository.self)!)
}
}
public func resolve<T>(_ type: T.Type) -> T {
guard let resolved = container.resolve(type) else {
fatalError("依存解決に失敗しました: \(type)")
}
return resolved
}
}
DIコンテナの中から対象のクラスを取得する必要があるのでどこからでもアクセスできるようにresolveメソッドを使って対象のクラスを取得できるようなメソッドを定義します。
DIコンテナの中から対象のクラスを取得するにはresolveメソッドを使用します。引数に取得したいデータ型を指定します。
// DIコンテナから対象のクラスを取り出して使用する
let viewModel: ViewModel = DIContainer.shared.resolve(ViewModel.self)
コンストラクタインジェクション
例えばViewModelやRepositoryなどの場合に依存性注入(DI)を行うためには「コンストラクタインジェクション」という方法を用います。まずは必要となる各クラスを定義していきます。
リポジトリプロトコル
protocol Repository {
func fetchMessage() -> String
}
リポジトリ実体クラス
class RepositoryImpl: Repository {
func fetchMessage() -> String {
return "こんにちは!これはRepositoryからのメッセージです。"
}
}
ViewModel
class ViewModel: ObservableObject {
private let repository: Repository
@Published private(set) var message: String = ""
init(repository: Repository) {
self.repository = repository
}
func loadMessage() {
message = repository.fetchMessage()
}
}
ここまで準備ができたら依存性注入を行うためDIコンテナを作成します。先ほど定義したDIコンテナがそのまま使えるので定義しておいてください。あとはresolveメソッドで参照できます。
struct RootView: View {
// DIContainer から注入した ViewModel を使う
@StateObject private var viewModel = DIContainer.shared.resolve(ViewModel.self)
private func greeting() {
viewModel.loadMessage()
}
var body: some View {
VStack {
Button {
greeting()
} label: {
Text(viewModel.message)
}
}
.padding()
}
}
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。





