Practical Dependency Inversion in Swift

Dmytro Anokhin
Dec 23, 2019 · 4 min read

Dependency inversion is a way to decouple software modules by inverting connections between them and using intermediate abstractions to hide implementation details. This helps to create independent modules, change or replace one module without touching dependent modules. Here are just some benefits:

  • Modules can be reused in multiple apps;
  • Code can be tested independently by mocking dependencies;
  • Frameworks and libraries can be updated and replaced easily.

Dependency inversion is a part of SOLID principles used as a best practice guide in modern software design.

What makes it inversion?

Dependency means that you have module A that uses, therefore depends on, module B:

This creates top to bottom connection, the high-level module depends on the low level one. Apple uses it in many OS frameworks, like SwiftUI and UIKit both depend on Core Animation.

Top to bottom connections are logical — if you want to build a house you depend on a foundation.

But what if we want to change the low-level module? Changes in module B require changes in dependant module A.

The dependency inversion principle introduces an abstraction in between two modules. Let’s name it A*. Module A defines and uses interface A*, module B implements it:

Now both modules depend on abstraction and the connection is inverted. A* hides implementation details. If we want to change or replace module B with module C we need to simply implement interface A*:

Consider this as the high-level module presenting its requirements, and the low level one implementing them.

Dependency inversion implies that module B and C both know interface A*. This is fairly simple to achieve for modules in the same project using extensions in Swift.

Independent modules can be connected using the adapter pattern:

Typically the adapter is implemented on an app or services layer.

Implementing Dependency Inversion

First we need to start with separation of abstraction layers. Refactoring code to aggregate all candidate methods under an abstract interface and implementing it in a concrete class. This class can be taken out to a separate module.

For new features dependency inversion can make coding faster because you can easily create mocks for dependencies, that may not exist yet.

Demo

To demonstrate dependency inversion principle I decided to create the app to displays a list of posts. I use JSONPlaceholder service for test data.

I created two networking modules: NetworkingA uses URLSession, NetworkingB uses Alamofire. Maybe I want to see which one works better ☺️

Different frameworks declare different APIs so mine are also a bit different.

With top to bottom structure my high level modules would depend on low level networking module to get data. But having two networking modules would be problematic.

I have higher level Services module that contains UserService class. It declares UserNetworkService protocol — abstraction for dependency inversion.

Services module includes NetworkingA and NetworkingB so I can use Swift extensions to implement conformance to UserNetworkService protocol.

My structure looks like this:

Now I can easily replace one networking module with another.

This example implies that Services module includes NetworkingA and NetworkingB. What if our modules are independent? PostsUI is an example of such module. It displays a list of posts and declares its dependency in DataProvider protocol.

The demo app implements DataProvider using the adapter for networking and services modules.

PostsUI is independent of other modules:

Structure for PostsUI looks like this:

PostsUI also uses SwiftUI and it is convenient to use previews while developing. For this PostsUI implements a mock for DataProvider with empty data. Same, but with test data, can be used if we want to add unit tests.

Having these different implementations, in different modules, is easy with dependency inversion principle.

Summary

Dependency inversion introduces an abstraction in between two modules to remove strong coupling and making code interchangable. As many powerful solutions, it is simple and elegant. Dependency inversion gives you the flexibility to create different implementations for prototyping, development, and unit tests.

Hope you find this article useful. You can find a demo project on my GitHub.

Also, follow me on twitter for updates about different open source projects I work on:

Cheers!

Flawless iOS

🍏 Community around iOS development, mobile design, and marketing

Thanks to Lisa Dziuba

Dmytro Anokhin

Written by

iOS Developer, here to share best practices learned through my experience. You can find me on Twitter: https://twitter.com/dmytroanokhin

Flawless iOS

🍏 Community around iOS development, mobile design, and marketing

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade