SwiftのDIコンテナライブラリであるSwinjectとSwinjectStoryboardをプロジェクトで使用しているのですが、少しハマったことがあったので書きます。
以下に完全なコードを載せていますが、注目してほしいのはViewModelAssembly
のところです。
ViewModelAssembly
内でAppViewModel
のインスタンスを登録していて、それを同じViewModelAssembly
内でMainViewModel
とSubViewModel
にインジェクトしています。
一見正しそうに見えますが、実はMainViewModel
とSubViewModel
には別々のAppViewModel
インスタンスがインジェクトされてしまいます。
SwinjectStoryboard+Setup.swift
import Swinject
import SwinjectStoryboard
extension SwinjectStoryboard {
class ApiAssembly: Assembly {
func assemble(container: Container) {
container.register(API.self) { _ in
return API()
}
}
}
class ViewModelAssembly: Assembly {
func assemble(container: Container) {
container.register(AppViewModelProtocol.self) { _ in
return AppViewModel()
}
container.register(MainViewModelProtocol.self) { r in
let api = r.resolve(API.self)!
let appViewModel = r.resolve(AppViewModelProtocol.self)!
return MainViewModel(api: api, appViewModel: appViewModel)
}
container.register(SubViewModelProtocol.self) { r in
let api = r.resolve(API.self)!
let appViewModel = r.resolve(AppViewModelProtocol.self)!
return SubViewModel(api: api, appViewModel: appViewModel)
}
}
}
class ViewControllerAssembly: Assembly {
func assemble(container: Container) {
container.storyboardInitCompleted(MainViewController.self) { r, c in
c.vm = r.resolve(MainViewModelProtocol.self)!
}
}
func assemble(container: Container) {
container.storyboardInitCompleted(SubViewController.self) { r, c in
c.vm = r.resolve(SubViewModelProtocol.self)!
}
}
}
@objc class func setup() {
let assembler = Assembler(container: SwinjectStoryboard.defaultContainer)
assembler.apply(assemblies: [
ApiAssembly(),
ViewModelAssembly(),
ViewControllerAssembly()
])
}
}
SwinjectではOpjectScopeという概念があり、デフォルトでは型が毎回resolveされるたびに、新しいインスタンスが作成されるようになっています。
インスタンスの作成は一度だけにして、二回目以降にresolveされる際は既存のインスタンスを返すようにするには、inObjectScope()
メソッドを呼んであげる必要があります。
SwinjectStoryboard+Setup.swift
class ViewModelAssembly: Assembly {
func assemble(container: Container) {
container.register(AppViewModelProtocol.self) { _ in
return AppViewModel()
}
.inObjectScope(.container) // スコープを指定する!!
}
}