SwiftUIでSwinjectなどのDIコンテナを使ってDIするときの解決策として、ネストした型を使う方法を考えてみたので共有します。
SwiftUIの場合、どこでDIする?
SwinjectでDIするとき、従来のStoryboardならSwinjectStoryboardというものが用意されていました。SwiftUIの場合はどうしたらよいでしょうか?
例えば、次のコードのSomeContentView
をDIしたいときはどうすればよいか?
struct BookMarkRow: View {
var body: some View {
HStack {
Text(bookMark.title)
NavigationLink(destination: SomeContentView()) {
Text("Next Page")
}
}
}
}
解決策
まず、 DependencyInjectable
というDI可能なprotocolを宣言します。
protocol DependencyInjectable {
associatedtype DependencyType
var di: DependencyType! {get set}
func resolveDependencyInstance() -> DependencyType
}
extension DependencyInjectable {
internal func resolveDependencyInstance() -> DependencyType {
let container = DIContainer.shared.getContainer()
return container.resolve(type(of: self.di))!!
}
}
次に、これを実装したstructを作ります。
struct Dependency
の中に依存するオブジェクトが注入されます。
struct SomeContentView: DependencyInjectable {
typealias DependencyType = Dependency
struct Dependency {
var p: TestProtocol
}
var di: Dependency!
init() {
di = resolveDependencyInstance()
}
func use() {
di.p.test()
}
}
依存性を解決するDIコンテナを宣言します。
※ SomeProtocol
やSomeProtocolImpl
は何でも良いので省略します
class DIContainer {
static let shared = DIContainer()
private init () {}
func getContainer() -> Container {
let container = Container()
container.register(SomeProtocol.self) { r in
return SomeProtocolImpl()
}
container.register(SomeContentView.DependencyType.self) { r in
return SomeContentView.DependencyType(p: container.resolve(SomeProtocol.self)!)
}
return container
}
}
実際に使うときは次のようにすれば、SomeContentView
は依存性が注入された形でインスタンスが生成されます。
let c = SomeContentView()
c.use()
まとめ
DependencyType
の中に依存するオブジェクトを宣言しておけば、あとはDIコンテナ内で依存関係を解決してやれば良いので、手間いらずだと思います。
extensionの中にDIコンテナが入るので、Service Locatorのようにも見えますが、依存するものはすべてDependencyType
の実装の中で確定し、依存するものはすべてDIコンテナの中で生成しているので、Service Locatorのように依存関係がわかりづらくなることはないと思います。
ほかに、もっと良い方法があれば教えて下さい。