Simple Dependency Injector in Swift
There are kinds of different variants of dependency injections — constructor, function and etc. Even if you see a simple function with a “String” parameter, you could consider this as a dependency injection, because your function depends on this parameter (so very trivial 😃).
But the question can appear, why we do actually need a dependency injector?
From my perspective, it is more relevant when you work in a team and you need to implement your features as fast as possible without creating too complicated code and also confirm SOLID plicinples. Or as an example to extend the existent entities simply injecting an instance of your freshly developed entity.
Imagine if you can inject an instance of your freshly created and brilliantly designed class instance with only two lines of code, i.e. WITHOUT changing even a line of already developed code instead of creating additional extensions or refactoring constructors in whatever already developed part of the app.
In this article I would like to present a super light-weight Android’s Dagger like Dependency Manager in Swift to easily inject any instance. I would say, only @Inject part. I will try to express myself as simple as possible.
And here is “Show me the code”. (Of course I will explain everthing. Basicly in this example all the dependencies are singletones here).
First of all check the line number 11:
Here a simple class is defined. As the name suggests, it will hold information about objects which will used in the whole app lifetime and manage adding or removing of any desired new dependency.
And all dependencies “live” inside the Dictionary
private static var dependencies = [ObjectIdentifier: Any]()
Fortunately, this static variable is private and the code will not “smell” much. Of course you can refactor this to a Singletone, but the tunning is up to you 😀.
You might wonder, why I used “ObjectIdentifier” instead of just simple “String”. There are plenty of reasons:
- As Swift declares: “unique identifier to reference types and metatypes”
- Conforms Hashable and Equatable out of the box as well.
- No way to use any methods.
- You reference a metatype and not the value as it happens with Strings, which increases performance.
Of course, for even more optimizations you may use weak collections like “NSMapTable”, make the values weak, and clear the pair after a successful call of the value by key if you know when exactly your dependencies should “die”.
With this function:
static func register<T>(_ dependency: T.Type, instance: T)
You just put a new instance of your dependency into the “dependencies” dictionary.
Instead of the “instance” parameter you can adopt this code and use closures (you need to adapt the collection “dependencies” as well) to perform some additional logic.
You may wonder why not reduce the number of parameters in this function…and not use
type(of: instance)
The reason is how you register your dependency. You’d better use for “dependency” parameter the Protocol’s metatype. It will be useful for Mocking.
This function:
static func getDependency<T>() -> T
will just return a dependency related to the key, which is based on the metatype. Pretty convenient.
With:
static func remove<T>(_ dependency: T.Type)
you just remove a not-needed anymore dependency, which you can always register again.
With:
static func clear()
you wipe the whole collection of dependencies. Use this wisely, since any dependency which is called is considered as a late init property, you may want not to crash your app at some point.
In order to make this look elegant, you wrap everything inside a @propertyWrapper
@propertyWrapper
struct Injection<T>
But first things first. In order to have dependencies stored, you first need to register them, and of course, before the moment you actually start using them.
DependencyContainer.register(
ServiceProtocol.self,
instance: Service()
)
You may add dependencies during your app flow when you need them, or as if you are certainly sure that some of them are required as an example just right after the app is launched, you can add them directly in your “AppDelegate” class’s functions.
With that you can call your object in a SwiftUI style:
@Injection private var service: ServiceProtocol
So far so good. Hope this helps to make your life easier and develop code much faster, but please don’t forget about profiling your app with Instruments 😉.