Closed adessoTurkey closed 4 years ago
It is possible to easily implement a constructor, method, and property injection manually. We've previously discussed in our meetup that we might pass our dependencies manually. I think it is a clean way of doing it as long as we stick with abstractions but there is one downside I'd like to bring up. The point of using the dependency injection pattern is decreasing the coupling between our objects. However, the moment we start passing dependencies manually, we are increasing the coupling by forcing our dependencies to be constructed inside other dependencies. I suppose composition root is the go-to pattern for this case but I feel like this is something we should be discussing in our next meet up.
I have examined a dozen (there are even more that are not on the list. see Swifjection, Weaver, Firebolt, Shank) of DI libraries and after deciding they are all very similar with their API and provide a similar set of features I've decided to remove those who have not received a commit in at least 6 months and have a minimum of 500 stars. Therefore we are left with; Swinject, Cleanse and Dip.
I have examined all of these libraries' documentation and along with some examples. I will not get into the implementation differences of these libraries since, at the core, they all work like a big dependency injection container initialized in AppDelegate
. You first create a dependency container in AppDelegate
, then you can resolve dependencies with closures like;
container.register(LoggerProtocol.self) { _ in Logger(level: .verbose) }
container.register(NetworkModuleProtocol.self) { module in
ConcreteNetworkModule(logger: module.resolve(Logger.self)!)
}
The injection part differs between libraries. Some chose injection via extensions
some via dependency containers.
The learning curve for these libs doesn't seem like too much if you are familiar with any other DI framework. (except for Cleanse. its API is too dagger-y. seemed a bit complicated to me. maybe because it is developed by the same team with Dagger
:unamused: ).
Almost all of them support constructor, method, and property injections. Most of these libraries contain Scopes (Swinject
also has custom-scope support), auto-resolving, circular dependencies, storyboard integrations, named definitions, subcomponents. Some even have their code generators. However, I find it hard to determine the cons of these frameworks without using them.
The two cons I have found common in most of these libraries are;
In my opinion, using one of these libraries would bring unnecessary complexity. Honestly, I don't see how using one of them would improve our codebase or general developer productivity. As far as I see these libraries are just big dependency containers initialized in AppDelegate
which might be implemented in our codebase with similar advantages.
My solution is that we try something experimental since we are trying to build a boilerplate from ground zero. If we can agree on that we should be using some sort of dependency container rather than passing dependencies manually, we might try some experimental solutions to this problem with Swift's new feature PropertyWrapper or a protocol-oriented approach like this one.
If we can't come up with something we are satisfied with, we might argue about which one of these frameworks would benefit us more. We'd certainly need much more detailed research about these three libraries and maybe use them in small examples to grasp their pros cons against each other but as I've said before it is hard for me to find the pros cons of these frameworks without actually using them.
As a result of the meeting, we have decided to make a deep analysis of PropertyWrapper and the possible advantages of DI methods.
We have decided not to use any DI library.
Possible DI libraries should be examined. A pros/cons list of libraries is required to help to decide which is to be used in boilerplate.