As already mentioned in my previous post about Dependency Injection in Swift I think that DI is essential for a maintainable and testable code base. I wasn’t completely happy with my previous solutions cause you had to write a lot of boilerplate code and explicit methods. Oliver Eikemeier gave some nice talks at the Cocoaheads and Swift.berlin meetups in Berlin last year about modularization and architecture of apps including a way better solution for DI, that I adapted to my needs and want to share with you here.
It would be nice if we had just one
inject() method on a central
ServiceLocator and Swift would infer which object it should return via type inference and a configuration table inside the
ServiceLocator, which can be configured with simple
Swift 2.0 introduced the new
ObjectIdentifier struct with which we get something hashable from a plain type in Swift. We can use
this as a key in a dictionary now. That’s just the part that I didn’t find when I wrote my last post!
So how does this new
ServiceLocator look like?
You can now register a service and inject it like this:
It’s important to consider that the
ObjectIdentifier is very specific. This means if you want to register implementations of a certain protocol you need to cast your implementation instance to that protocol during registration:
Also optionals and implicit unwrapped optionals create another
ObjectIdentifier than the plain types. Therefore I recommend to avoid optionals and implicit unwrapped optionals in the
ServiceLocator so that you only register and inject “clean” types.
You can extended the
ServiceLocator by a
registerSingleton(singletonInstance: Service) method to avoid the typical
sharedInstance() implementations and make it more explicit which objects will be recreated for every
inject() call and for which you will always get the same:
Of course you need one point where you register all your services at the
ServiceLocator. One good point would be the
AppDelegate, but this can become a bit tedious if you have a large app. Therefore I’m using modules for each part of my app and only register these modules with the
ServiceLocator in the
AppDelegate. This helps to keep domain logic close together, improves modularization and makes your app scalable.
Though, you need to be a bit careful in which order you register the dependencies and what depends on what: There’s no solution for cyclic dependencies here!
In unit tests you can easily register your mock implementations for injection. It’s also recommended to wipe the registry of the
ServiceLocator before every test to ensure a clean state and avoid injection of real implementations by accident.
You can find my default
ServiceLocator including a
CocoaDefaultModul, which includes some basic iOS singletons, in this Gist.
Of course there’s a lot of things you could add to such a
- Check that it’s only used in initializers - Oliver did that.
- Add identifier strings to use multiple different registrations for one type - like Spring in Java does with @Qualifier annotations.
- Use multiple
ServiceLocatorinstances in your app. Each one for a specific part. You might want to drop the
sharedLocatorthen and pass specific locators around.
Because of all this variants, differing personal preferences and it’s actual simplicity I won’t make a pod/lib out of this and leave it as an exercise to the reader to copy the parts you like for your project ;-).