Don't know how to really share a .shared scope object
Narayane opened this issue · 11 comments
Hi,
I want to share the same instance of a view model between 2 view controllers. But I don't know how to inject it in them without call resolve() function that "reinit" my view model object instance according to this
Here, part of my Dip container:
self.register(.singleton) { try Repository(graphQL: self.resolve(), fileStorage: self.resolve()) as RepositoryProtocol }
self.register(.shared, tag: "SharedVM") { try SharedViewModel(repository: self.resolve()) as SharedViewModelProtocol }
self.register(storyboardType: AViewController.self, tag: "A")
.resolvingProperties { container, vc in
vc.viewModel = try container.resolve(tag: "SharedVM") as SharedViewModelProtocol
}
self.register(storyboardType: BViewController.self, tag: "B")
.resolvingProperties { container, vc in
vc.viewModel = try container.resolve(tag: "SharedVM") as SharedViewModelProtocol
}
When I load my AViewController, Dip logs:
Trying to resolve AViewController with UI container at index 0
Optional(Context(key: type: AViewController, arguments: (), tag: String("A"), injectedInType: nil, injectedInProperty: nil logErrors: true))
Optional(Context(key: type: SharedViewModel, arguments: (), tag: String("SharedVM"), injectedInType: AViewController, injectedInProperty: nil logErrors: true))
Reusing previously resolved instance Repository
Resolved type SharedViewModelProtocol with SharedViewModel
Resolved type AViewController with <AViewController: 0x157dbcae0>
Resolved AViewController
Then when I push my BViewController from AViewController, Dip logs:
Trying to resolve BViewController with UI container at index 0
Optional(Context(key: type: BViewController, arguments: (), tag: String("B"), injectedInType: nil, injectedInProperty: nil logErrors: true))
Optional(Context(key: type: SharedViewModel, arguments: (), tag: String("SharedVM"), injectedInType: BViewController, injectedInProperty: nil logErrors: true))
Reusing previously resolved instance Repository
Resolved type SharedViewModelProtocol with SharedViewModel // here, I want Reusing previously resolved instance SharedViewModel
Resolved type BViewController with <BViewController: 0x157e58a60>
Resolved BViewController
I don't want to update the view model scope to .singleton because it is not: just a "shared" object for a determinated time
Thanks for helping me
The difference between shared
and singleton
scope is that shared
only shares instances in the same dependencies graph. That said, after top-most resolve
method returns next resolve
will produce new instances for shared
scope but will reuse previously created instances for singleton
scope. So to use shared
scope you need to resolve you graph at once, not later at runtime when push happens.
That said you need to create a graph that will be able to create a second view controller when you push it. You can use factories/builders for that, that you can resolve as part of the dependencies in the beginning and that will have reference to view model that they will pass to view controller that they create lazily at runtime.
self.register(.singleton) { try Repository(graphQL: self.resolve(), fileStorage: self.resolve()) as RepositoryProtocol }
self.register(type: SharedViewModelProtocol.self, factory: SharedViewModel.init)
self.register(storyboardType: AViewController.self, tag: "A")
.resolvingProperties { container, vc in
vc.viewModel = try container.resolve() as SharedViewModelProtocol
}
self.register(storyboardType: BViewController.self, tag: "B")
.resolvingProperties { container, vc in
vc.viewModel = try container.resolve() as SharedViewModelProtocol
}
Like this? Because same effect than previous code, VM instance is always not shared.
It's not about registration, it's about how you resolve dependencies for the second view controller. They should be resolved when you resolve the first view controller that will later push the second.
When using storyboard registration it will resolve view controller properties only when it is created from xib, so it will not be the new objects graph.
So with this you either need to use singleton
scope or switch from registering second vc with storyboard registration to creating it in code via factory. You can still create it from a storyboard but the factory should already have all the properties this vc needs and should inject them.
class VCFactory {
let viewModel: SharedViewModelProtocol
func createSecondVC() -> UIViewController {
let vc = Storyboard.instantiateViewController...
vc.viewModel = viewModel
return vc
}
}
register(type: SharedViewModelProtocol.self, factory: SharedViewModel.init)
register(factory: VCFactory.init)
self.register(storyboardType: AViewController.self, tag: "A")
.resolvingProperties { container, vc in
vc.viewModel = try container.resolve() as SharedViewModelProtocol
vc.factory = try container.resolve() as VCFactory
}
Would it be technically possible to have a "real" shared scope for a given object instance between different dependencies resolution graphs?
that's what singleton
scope is for 🤷♂️ you maybe can use weakSingleton
if you need to recreate an instance at some point.
Technically not. For me, singleton scope lasts while dip container exists whereas shared scope "as I hear" lasts while at least one dependencies graph references it.
what you described is a weekSingleton
=)
@ilyapuchka It seems indeed. In my mind, this scope was only useful for breaking singletons cyclic injections... but I have one question again :)
I observe this:
- first time I launch AViewController, Dip logs
Resolved type SharedViewModelProtocol with SharedViewModel
- then I push BViewController from AViewController, Dip logs
Reusing previously resolved instance SharedViewModel
that it is perfect! - then I go to another part of my app (DViewController then RViewController then ...) with no relation with AViewController or BViewController dependencies graphs
- then I relaunch AViewController and Dip logs
Reusing previously resolved instance SharedViewModel
that I don't want, I want a fresh instance of my SharedViewModel - then, when I will repush BViewController from AViewController (if I will), it uses the fresh SharedViewModel instance
So, how can I force your you maybe can use weakSingleton if you need to recreate an instance at some point at each AViewController dependencies graph new resolution?
self.register(.singleton) { try Repository(graphQL: self.resolve(), fileStorage: self.resolve()) as RepositoryProtocol }
self.register(.weakSingleton) { try SharedViewModel(repository: self.resolve()) as SharedViewModelProtocol }
self.register(storyboardType: AViewController.self, tag: "A")
.resolvingProperties { container, vc in
vc.viewModel = try container.resolve() as SharedViewModelProtocol
}
self.register(storyboardType: BViewController.self, tag: "B")
.resolvingProperties { container, vc in
vc.viewModel = try container.resolve() as SharedViewModelProtocol
}
@Narayane you should probably register two weakSingleton
s of your view model with different tags, maybe reusing tags you use for view controllers. Then when you resolve the second stack when you need to resolve this new view model you need to use a different tag and then the new instance will be created and associated with this tag.