This is not an issue, but a suggestion/idea.
Assume I have a RootController and RootRouter. The last one is responsible for presenting (modal/push, animation, etc) other controllers. I want to present AnimalController (which has it's own dependencies).
I have the following assemblies diagram:
After #50 I can make animal assembly with parent assembly (I really need to access db from AnimalController).
So here is the problem:
class RootControllerContainer: AssemblyType {
func assemble(container: Container) {
container.register(RootController.self) { r in
RootController(router: r.resolve(RootRouter.self)!)
}
container.register(RootRouter.self) { _ in
RootRouter()
}
}
}
Now I'd like to create a AnimalController inside RootRouter.swift:
func showDogDetails(dog: Dog) {
let animalController = ???
rootController.navigationController.pushViewController(animalController, animated: true)
}
So the question is how do I construct AnimalAssembly with RootAssembly? When we were working with Containers I was able to create new container with given parent.
So I suggest:
Pass assembler reference in assemble func, so I can register components, that is dependant on this assembler.
Now I'd like to create a AnimalController inside RootRouter.swift:
I think I understood from the beginning to here with the showDogDetails function:
So the question is how do I construct AnimalAssembly with RootAssembly? When we were working with Containers I was able to create new container with given parent.
But here I just didn't understand the cause of the problem that you could resolve AnimalController with simple Containers but couldn't with Assemblies.
So I suggest:
Pass assembler reference in assemble func, so I can register components, that is dependant on this assembler.
Also, I just didn't get how to solve the problem with the Assembler reference passed to assemble function of AssemblyType.
Would you explain the question and suggestion parts more? (Is the application in VIPER Architecture? Maybe because I don't know typical DI in the architecture, I couldn't catch the context of your idea.)
I guess you can resolve dependencies if you keep references of Containers or Assemblers in RootRouter.swift. (Just my guess)
@yoichitgy Yes. I'm using VIPER and I'm talking about that part:

Let's consider Dogs encyclopaedia with list of animals and animal detail controller.
When user selects row in listVC router should present detailVC for a specific dog.
For performing that task I have a function in router: func showDogDetails(dog: Dog). This function should instantiate DogDetailVC and pass a dog to it.
let dogDetailVC = DogViewController(); dogDetailVC.dog = dog; dogDetailVC.api = apilet dogDetailAssembler = DogDetailAssembler(); let dogDetailVC = dogDetailContainer.resolve(DogViewController.self, dog)!But I have a problem in with DI approach: DogDetailAssembler doesn't know about API (it is registered in assembler, where listVC is registered). That mean I should pass ListVCAssembler as a parent to DogDetailAssembler.
But now let's take a look at how ListVCAssembler works:
class ListVCAssembler {
private let assembler: Assembler = {
let assembler = Assembler()
assembler.applyAssembly(LIstVCAssembly())
}()
}
class ListVCAssembly: AssebmlyType {
func assemble(container: Container) {
container.register(ListVCRouter.self) { _ in
ListVCRouter()
}
}
}
As we can see ListVCRouter doesn't know about it's assembler, but it is responsible for creating other assemblies, that relies on top level assembly.
So my suggestion is to pass Assembler as a part of assemble function. We can rewrite ListVCAssembly like this
class ListVCAssembly: AssemblyType {
func assemble(container: Container, assembler: Assembler) {
container.register(ListVCRouter.self) { [unowned self] in
ListVCRouter(assembler: assembler)
}
}
}
Now func showDogDetails(dog: Dog) can create correct child assembler:
// in ListVCRouter.swift:
let dogDetailAssembler = DogDetailAssembler(parentAssembler: assembler)
let dogDetailVC = dogDetailContainer.resolve(DogViewController.self, dog)!
Does it make sense?
PS. Of course there is workaround (I'm currently using it, while we have that discussion:) ). I just pass assembler reference to ListVCAssembly constructor and store it as unowned reference, so I can use it in assemble function
PS2. Passing assembler in assemble function is dangerous. Using it without unowned/weak will create retain cycle: Assembler -> Container -> FactoryImpl -> Assmbler. But it can be mentioned in docs
Thanks @Nikita2k for the detail. I got it. What's going on here is injection of an injector (Container or Assembler in Swinject) itself, and a router needs a service locator.
Here is an example of injection of Assembler as an injector.
class ServiceLocatorAssembly: AssemblyType {
func assemble(container: Container) {
container.register(Assembler.self, name: "root") { _ in
// Empty assembler in this example, but dependencies should be assembled here actually.
Assembler()
}
container.register(Assembler.self, name: "dog") { r in
// Here also.
Assembler(parentAssembler: r.resolve(Assembler.self, name: "root")!)
}
}
}
Then ListVCAssembly can resolve the service locator.
class ListVCAssembly: AssemblyType {
func assemble(container: Container) {
container.register(ListVCRouter.self) { r in
ListVCRouter(serviceLocator: r.resolve(Assembler.self, name: "dog")!)
}
}
}
In ListVCRouter:
let dogDetailVC = serviceLocator.resolver.resolve(DogViewController.self, dog)!
The code above is still concept level, and you might need to enhance it, but I think it follows idea of dependency injection better because ListVCRouter now does not depend on DogDetailAssembler. You might be able to use Container, Resolvable or ResolverType instead of Assembler. I used name parameter for the registrations, but you might prefer subclassing Assembler to differentiate the service locators instead of using name parameter on registration.
Do you think the idea of injection of injectors as service locators can fit in your program?
@yoichitgy Yes! It looks as a very good option! I think it solves a lot of problems
Good to hear! Would you tell me which one (Assembler, Container, Resolvable or ResolverType) you choose as a service locator later? I want to add a document about injection of injectors, and your information will be helpful.
Thank you for your quick response and will to deeply understand my problem!
I think I've stuck at some point and can't come up with solution for now.
Previously my Hierarchy was:
Assembler type, that constructs Assembler with desired AssemblyTypes)Assembler type, that constructs Assembler with desired AssemblyTypes with parent RootAssembler's Assembler)Ie:
class RootAssembler {
let assembler: Assembler
init(parentAssembler: Assembler) {
assembler = try! Assembler(assemblies: [FirstAssembly(),
SecondAssembly()], parentAssembler: parentAssembler)
}
}
I have different options how to add ServiceLocator, but seems they don't fit me.
Assembly on top of RootAssemblerIt means, that I should instantiate top level assembly (service locator assembly) and resolve RootAssembly through it (otherwise I will have two instances of RootAssembly).
=>
It means, that ServiceLocatorAssembly should look like:
func assemble(container: Container) {
container.register(RootAssembler.self) { (r, parentAssembler: Assembler) in
RootAssembler(parentAssembler: parentAssembler)
}.inObjectScope(.Hierarchy)
container.register(DogAssembler.self) { r in
DogAssembler(parentAssembler: r.resolve(RootAssembler.self)!.assembler)
}
RootAssembler should know about ServiceLocatorAssembler as a parent, so it could inject it to ListVCRouter (original problem)
=>
To construct DogAssembler I should pass ServiceLocatorAssembler instance, which is not a perfect solution, as it might be very deep inside
class RootAssembler {
let assembler: Assembler
init() {
assembler = try! Assembler(assemblies: [FirstAssembly(),
SecondAssembly(), ServiceLocatorAssembly()])
}
}
=>
I need a RootAssembler instance to get everything
=>
// ServiceLocatorAssembly.swift
func assemble(container: Container) {
container.register(RootAssembler.self) { r in
RootAssembler()
}.inObjectScope(.Hierarchy)
will create second instance of RootAssembler
Doest it make sense?
I ended up designing RootAssembler:
class RootAssembler {
private (set) var assembler: Assembler!
static func rootAssembler() -> RootAssembler {
let assembler = try! Assembler(assemblies: [FirstAssembly(),
SecondAssembly(), ServiceLocatorAssembly()])
let rootAssembler = assembler.resolver.resolve(RootAssembler.self)!
rootAssembler.assembler = assembler
return rootAssembler
}
Can't say I'm happy with that design, but it least it works:)
I definitely make a post about using Swinject, really cools lib:)
I don't have much time today, and I will take time with my family in this weekend, so I will write a short comment here.
First, I would like to make Service Locator Pattern clear. I recommend the following article to understand the pattern.
https://msdn.microsoft.com/en-us/library/ff648968.aspx
Then you will add a hierarchy to Locator, and inject it to ClassA in the picture of the article, where ClassA corresponds to ListVCRouter in your case.
Thanks @Nikita2k for your future post using Swinject.
I need more understanding about VIPER using DI containers. After I release Swinject v1.1.0, I'll write a blog post to show examples to structure the containers in VIPER architecture. I'll write a simple example without hierarchy and an advanced one that can be an answer (or perhaps a suggestion) to your case.
@yoichitgy Sorry for delay with response. Here is the draft of my architecture: https://github.com/Nikita2k/SwiftViper . I will explain it shortly as soon as I have time. Thanks for help again!
@Nikita2k Thank you for sharing the great templates for VIPER鉂楋笍Please feel free to keep your own pace to update the repo.
I'll add a link to the repo to README.md of Swinject when you are ready. Or, you can send PR to update it.
Off topic, but Swinject was listed in GitHub trending Swift repos with your profile icon yesterday. I should have taken a screenshot to show.
Thanks馃槂
Hello~
Firstly, thank you very much for a useful library and its very nice documentation. I was wondering if @yoichitgy had reached a conclusion on a recommended pattern for injecting injectors.
Hello @brigade-juni. Thanks for your kind comment. I'll write something about VIPER in my blog post hopefully in February.
Before that, I have to give a conclusion to #58.
@Nikita2k shares his VIPER templates at https://github.com/Nikita2k/SwiftViper. They are very helpful to develop an app in the architecture.
Thank you, the templates are great. I am looking forward to your blog post :)
Hello @Nikita2k
I have a question about your comment. It's not clear for me what responsibilities of RootAssembler are?
I ended up designing RootAssembler:
class RootAssembler {
private (set) var assembler: Assembler!
static func rootAssembler() -> RootAssembler {
let assembler = try! Assembler(assemblies: [FirstAssembly(),
SecondAssembly(), ServiceLocatorAssembly()])
let rootAssembler = assembler.resolver.resolve(RootAssembler.self)!
rootAssembler.assembler = assembler
v return rootAssembler
}
Can't say I'm happy with that design, but it least it works:)
@igorkotkovets thanks for your question.
The goal of RootAssembler is to separate different parts of the app. If we consider Dropbox as example we can make "UIAssembly", "FileTransferServiceAssembly", etc.
ServiceLocatorAssembly:class ServiceLocatorAssembly: AssemblyType {
func assemble(container: Container) {
container.register(RootAssembler.self) { _ in
UIApplication.appDelegate().rootAssembler
}
container.register(TimelineAssembler.self) { r in
TimelineAssembler(parentAssembler: r.resolve(RootAssembler.self)!.assembler)
}
@Nikita2k thank you for fast answer! Now it's clear how you inject assemblies in other modules.
Closing due to inactivity
Most helpful comment
@igorkotkovets thanks for your question.
The goal of RootAssembler is to separate different parts of the app. If we consider Dropbox as example we can make "UIAssembly", "FileTransferServiceAssembly", etc.
ServiceLocatorAssembly: