The key to dependency injection is protocols – from there sprouts many variations, flavours, and techniques. Although this is yet another dependency injectionâ„¢ blog post, I would like to share a pure Swift, battle-tested DI implementation with no outside dependencies or magic. It combines protocol extension and type erasure to give you a solid, flexible dependency injection that works great with unit test and even frameworks.
UPDATE: Created a more modern, property wrapper version here.
The Dependency Container
The first thing we must do is come up with a container that will house all our dependencies. This will be referenced later from consumers to grab dependencies they want.
protocol Dependency { func resolveService() -> HTTPServiceType func resolveWorker() -> AuthenticationWorkerType func resolveService() -> AuthenticationService func resolveWorker() -> UsersWorkerType func resolveStore() -> UsersStore func resolveCache() -> UsersStore }
Now that we got the blueprint to our container and its dependency protocols, let’s implement the concrete dependency instances:
class CoreDependency: Dependency { func resolveService() -> HTTPServiceType { return HTTPService() } func resolveWorker() -> AuthenticationWorkerType { return AuthenticationWorker( service: resolveService() ) } func resolveService() -> AuthenticationService { return AuthenticationNetworkService( httpService: resolveService() ) } func resolveWorker() -> UsersWorkerType { return UsersWorker( store: resolveStore(), cacheStore: resolveCache() ) } func resolveStore() -> UsersStore { return UsersNetworkStore( httpService: resolveService() ) } func resolveCache() -> UsersStore { return UsersCoreDataStore() //..or use UsersRealmStore } }
This is where the main meat of the dependency injection occurs. The dependency objects implement the protocols so the concrete types are hidden from the caller. This way, the dependency types can be swapped out underneath without affecting the rest of the application.
Also notice that some dependencies reference other dependencies within the container. The implications of this is powerful because you are building a dependency graph and any part of it can be changed which will cascade.
The Dependencies
There are no singleton dependencies in our container because we always want to deal with immutable objects, otherwise state management will bite you later. Instead, its a factory serving fresh instances. The dependencies are structs
that implement the protocols.
Here’s an example of some of the dependencies:
protocol HTTPServiceType {...} struct HTTPService: HTTPServiceType { ... } /// protocol AuthenticationService {...} struct AuthenticationNetworkService: AuthenticationService { private let httpService: HTTPServiceType init(httpService: HTTPServiceType) { self.httpService = httpService } } /// protocol AuthenticationWorkerType {...} struct AuthenticationWorker: AuthenticationWorkerType { private let service: AuthenticationService init(service: AuthenticationService) { self.service = service } }
We’re using classic constructor injection to feed the dependencies in.
The HTTPService
dependency handles the raw network requests/responses and where you would import Alamofire
for example. Never leak your 3rd-party dependencies to the rest of the app! Instead, encapsulate it under one of your custom dependencies. That’s what HTTPService
will do for Alamofire
.
Now based on our dependency container, HTTPService
gets injected into AuthenticationNetworkService
so it can make the REST API endpoint calls. It doesn’t know that it’s using Alamofire
underneath and can be completely swapped out for another network library or URLSession
.
From here, AuthenticationNetworkService
gets injected into AuthenticationWorker
that gets referenced by the rest of the app. This way, the REST API server or endpoints can be completely swapped out and your app wouldn’t have to know. It would still call the AuthenticationWorker
API’s.
The Injection
It’s now time to start using our dependency container. We will use a protocol extension to pass in the dependency container so the caller can start resolving the dependency types it wants.
We need a singleton to hold a reference to the dependency container. So although we couldn’t get away from singletons altogether, the dependency container is a factory and still serving immutable instances.
/// The singleton dependency container reference /// which can be reassigned to another container struct DependencyInjector { static var dependencies: Dependency = CoreDependency() private init() { } } /// Attach to any type for exposing the dependency container protocol HasDependencies { var dependencies: Dependency { get } } extension HasDependencies { /// Container for dependency instance factories var dependencies: Dependency { return DependencyInjector.dependencies } }
The DependencyInjector
should be configured early on in the app lifecycle. That way, your frameworks can have its own dependency container, such as CoreDependency
, and consumers can override any of the dependencies it desires. This works great for consumers who want to use a different UserDefaults
app group or Keychain access group.
We do this in the willFinishLaunchingWithOptions
:
extension UIApplicationDelegate { func configure(dependency: Dependency) { DependencyInjector.dependencies = dependency } } /// class AppDependency: CoreDependency { override func resolveCache() -> UsersStore { return UsersRealmStore() } } /// @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool { configure(dependency: AppDependency()) return true } }
Finally, the protocol extension for HasDependencies
is used to provide the dependency container to whoever wants it:
class ViewController: UIViewController, HasDependencies { private lazy var authenticationWorker: AuthenticationWorkerType = dependencies.resolveWorker() override func viewDidLoad() { super.viewDidLoad() authenticationWorker.login(with: "whatever") { //completed } } }
The Unit Test
Not only does this dependency injection work great for frameworks, but of course works great for unit tests. It can be configured on a global and scoped level too!
For the global level of your unit tests, you can create a TestDependency
that subclasses CoreDependency
:
class TestDependency: CoreDependency { // Override with mocks, spy, or whatever override func resolveService() -> AuthenticationService { return AuthenticationTestService() } } struct AuthenticationTestService: AuthenticationService { var isAuthorized: Bool { return true } func signup(with request: Any, completion: @escaping () -> Void) { print("AuthenticationTestService.signup") } func login(with request: Any, completion: @escaping () -> Void) { print("AuthenticationTestService.login") } func logout() { } }
You set this in your setUp()
function of your test case:
class MyCore_Tests: XCTestCase, HasDependencies { override func setUp() { super.setUp() // Use test dependency or even a scoped one to the test! DependencyInjector.dependencies = TestDependency() } }
What’s even more powerful is you can create a scoped dependency container for a specific test case, see ScopedDependency
:
class MyCore_Tests: XCTestCase, HasDependencies { private lazy var authenticationWorker: AuthenticationWorkerType = dependencies.resolveWorker() private lazy var usersWorker: UsersWorkerType = dependencies.resolveWorker() override func setUp() { super.setUp() // Use test dependency or even a scoped one to the test! DependencyInjector.dependencies = ScopedDependency() } override func tearDown() { super.tearDown() // Set dependencies back to what it was DependencyInjector.dependencies = TestDependency() } private class ScopedDependency: TestDependency { override func resolveService() -> HTTPServiceType { return MockHTTPService() } } private struct MockHTTPService: HTTPServiceType { public func post(url: String, parameters: [String: Any], headers: [String: String]?, completion: @escaping () -> Void) { //Blah, blah print("MockHTTPService.post") } public func get(url: String, parameters: [String: Any], headers: [String: String]?, completion: @escaping () -> Void) { //Blah, blah print("MockHTTPService.get") } } } extension MyCore_Tests { func testAuthenticationExample() { authenticationWorker.signup(with: "whatever") { XCTAssertTrue(true) } } func testUserExample() { usersWorker.fetchCurrent { XCTAssertTrue(true) } } }
Conclusion
The combination of a dependency container, a protocol extension to expose it, and immutable dependencies give you a pure Swift DI that works well with frameworks and unit tests. See this working example to try it out.
HAPPY CODING!!
Great post! I love it
Hello, nice post. One remark, if you use protocol extension on Dependency protocol then you can implement default implementation for every dependencies which will make the writing of Mock objects really easier as there will be no use of subclassing CoreDependency or override, just implementing the needed method.
One quick example :
protocol AppDependency: HasImageProvider, HasArticleProvider, HasPersistanceProvider {
}
extension AppDependency {
var imageProvider: ImageProvider {
return ImageProviderImpl()
}
var articleProvider: ArticleProvider {
return ArticleProviderImpl()
}
var persistanceProvider: PersistenceProvider {
return PersistenceProviderImpl()
}
}
struct AppDependencyImpl: AppDependency {}
AppDependencyImpl becomes easy :-).
I wrote something on this here : http://fredfoc.com/dependency-injection-in-swift-mini-cake-solution/
Best regards.
Fred.
Hi, this post is very useful. Thank you for that.
But it looks like the logic used in the following passage is flawed. Just because ViewController is said to be implementing HasDependencies protocol does not mean it has access to its default member implementations. As such an attempt to reference “dependencies” property implemented in the protocol’s extension from inside an unrelated type causes a compilation error “Instance member ‘dependencies’ cannot be used on type …”.
Am I missing something or was it an oversight?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Finally, the protocol extension for HasDependencies is used to provide the dependency container to whoever wants it:
class ViewController: UIViewController, HasDependencies {
private lazy var authenticationWorker: AuthenticationWorkerType = dependencies.resolveWorker()
override func viewDidLoad() {
super.viewDidLoad()
authenticationWorker.login(with: “whatever”) {
//completed
}
}
}
Hi, I’m not sure I follow, but since the workers are declared as `lazy` then it has access to the `dependencies` property. It’s available to whoever implements `HasDependencies` protocol. Do you have a code sample where this fails?
Hi,
Essentially, if you try to build your entire example code as is, the line below does not compile. The compiler error is “Instance member ‘dependencies’ cannot be used on type ‘ViewController'”.
private lazy var authenticationWorker: AuthenticationWorkerType = dependencies.resolveWorker()
Thank you.
It looks like the solution here is to prefix the property name with “self”, so that this in fact compiles:
private lazy var authenticationWorker: AuthenticationWorkerType = self.dependencies.resolveWorker()
Hi,
Great post. I’ve been doing some trials and I really like it.
I’ve been trying to incorporate Core Data and was wondering how you implement it. Especially when integrating it with the Clean Architecture you explain in the previous post in the series.
I was converting the Managed Objects (MO) to Data Transfer Objects (DTO struct) for passing around but realised that by doing this, you’re not using the fetched results controller which makes Core Data really handy. I’m also indecisive whether to include the related MOs in the DTO.
What are your thoughts on this?
Looking forward to your answer.
Hi Christophe, I’ve usually had to forego Core Data and even Realm’s fetch controller candy to protect the application from leaking dependencies everywhere. In the short term it’ll hurt, but I’ve ended up relieved in the long run.
Instead I wrapped the real-time mechanisms of Realm and Firebase in a worker and use that worker in the interactor. Real-time still works and the interactor subscribes thru the wrapper and pumps data thru the Clean Arch flow as usual. You still won’t get the fetch controller, but frameworks almost always provide a way to do the real-time mechanisms without the UI components.
Generally I’ve found avoiding the UI components that frameworks provide keeps things clean and you can do it in a fraction of the code that the frameworks use underneath.
One more thing, maybe using pluggable services in your view controllers would allieviate this problem. That would keep the subscription code consolidated and reusable: http://basememara.com/pluggable-appdelegate-services/
Hello super article! Is there a way to inject a service to two different controllers, but when in the first controller there is a change of a variable then in the second controller it will be up to date? Currently it does not work because we inject singleton, each time it creates an instance
Hi Mickael, thx for the feedback 🙂
I would discourage carrying state around in the services and instead keep them immutable. The reason is it opens up a door where you now have to manage those service states manually everywhere, and becomes a chasing game to ensure the state doesn’t become stale. Also, you’d have to make it thread-safe in case those services are injected in different queues or threads.
With that being said, instead of creating a new instance every time, you can pass your singleton object in the resolve function:
“`
class CoreDependency: Dependency {
func resolveWorker() -> AuthenticationWorkerType {
return AuthenticationWorker.shared
}
“`
I think you would have to make the protocol mutable like this `protocol AuthenticationWorkerType: class {…}`. Let me know if this doesn’t work and we can try to figure another approach together.
Thanks for your answer, Here is what I tested below:
protocol Dependency {
func resolveExtractIcon() -> ExtractIconService
var resolvedAssetsViewModel: AssetsViewModelService { get }
}
class DependencyFactory: Dependency, HasDependencies {
// Below it has allowed me to create a single instance accessible only to the view controller create at the same time
let resolvedAssetsViewModel: AssetsViewModelService = AssetsViewModel(extractIconManager: ExtractIcon())
func resolveExtractIcon() -> ExtractIconService {
return ExtractIcon()
}
}
I created resolveAssetsViewModel which is a variable, it works partially.
In my application I have 3 views controllers that use this view model, so all 3 must have the instance always a days.
In my my application 2 Viewcontroller on 3 are created, and the viewModel of the 2 controllers are always up to date. But when I open the 3rd controller that wants to use the viewModel then it is not up to date with others.
For example, the viewModel has an array of 3 values. The 2 viewController that was created at the same time, the array is a day. But the 3rd controller of the array is empty, as if it creates a new instance of the dependency
If you need more info, send me an email mickaelnanah@gmail.com and try to solve it together, because I think that if it is solved it will help a lot of people
Your solution work fine, Now the problem is that the shared create the instance is not injected, there is a solution to inject the shared?
Your solution work fine, Now the problem is that the shared create the instance is not injected, there is a solution to inject the shared?