Dependency Injection on iOS — part 1/4

An article about architecture, tests and much more

Fernando del Rio
14 min readNov 26, 2018

Hello, my name is Fernando, I'm an iOS developer from Brazil and this is my first post on Medium. I want to start sharing my thoughts on iOS development and I choose this subject, because I think it's a really cool topic to be explored.

Before anything, this is an article about architecture, so you can expect my opinion on how you should structure your code. But don't worry, I won't be telling you to use the architecture pattern X, Y or Z.

When developing apps, all the context and details matter. I prefer to think in the app I want to build first, which classes I need to create, how I can make this more testable, how I can make that more reusable. The architecture is just a consequence of that. Just adding a layer of abstraction doesn't make your problems to go away, and that's one reason for an architectural pattern does not fit on every single app.

That being said, you can expect on this article an example of a fairly simple app, built without any concerns of architecture and then later we will try to improve it, with some concerns in mind like: Reusability, Maintainability, Scalability, Testability and without sticking to any known pattern (at least in the beginning).

I will introduce the concept of Dependency Injection and later we will apply it as we evolve the example app's architecture, demonstrating in a practical way how to really benefit from it. I want to prove the reusability by showing how that simple app could evolve in the future with less effort. I want to prove the testability by writing Unit / UI tests with less effort (it may surprise you how many tests we can write with a so simple example).

But we will get there. This article will be divided in four parts. If you have any questions or have a different opinion, feel free to leave comments in the post :-)

Summary

1. Coupling between software modules
1.1. Coupling in a higher level of abstraction
1.2. Coupling in a lower level of abstraction
2. Dependency Injection
2.1. Constructor injection
2.2. Setter injection
3. Benefits from using dependency injection
3.1. Reusability
3.2. Maintainability
3.3. Scalability
3.4. Testability
4. Swinject: Framework for Dependency Injection
4.1. Cyclic Dependency
4.2. Handling different containers
4.3. Fancy container ideas
4.4. Swinject Extensions
5. TL;DR

1. Coupling between software modules

To understand Dependency Injection we first need to understand the concept of Coupling.

In software engineering, coupling is the degree of interdependence between software modules

1.1. Coupling in a higher level of abstraction

Imagine we have two systems that need to interact with each other. The coupling would be the metric that tells you how much a system depends on the other system. It can seems to be a little bit abstract, but there's a simple way to measure that: Make changes in the first system and count the number of changes you need to do in the other system in order to make it work. A higher number indicates a tight coupling. Low numbers indicate what we call a loose coupling.

For instance, imagine these two systems were built in a common platform, using the same technology stack. Also imagine that, they exchange information using a protocol from that technology stack. We can assume that these two systems have a tight coupling. If the stack of technology of one of the systems needs to change the whole integration would break: It would require the other system to completely to adapt in order to keep exchanging information.

Tight coupling between two systems

But, what if they were designed to exchange information using a standard format like XML or JSON? In a scenario like that, we can assume that the systems have a loose coupling. Even if one of the systems changes the whole stack of technology, in the end they could probably still communicate using that standard format and maybe no changes would be required in the other system.

Loose coupling between two systems

1.2. Coupling in a lower level of abstraction

Now let's think in an example closer to the iOS development. Take a look in the following code:

In this snippet we can see two classes that depends on each other. This relationship could be represented by the following image:

Relationship 1:1 between Person and Cat

Even though, this seems a simple example with a relationship between two small entities, think on them as two software modules of any nature. This could be two complex components: One written by you inside your app and the other present in a third party library, for instance.

The form we wrote that relationship is fixed. A Person always have a Cat. A Cat always belong to a Person. What if we need to change that Cat to a Dog? The class Person would need to adapt in order to support the change. If Person and Cat deeply depend on each other, the number of changes could be tremendous (a.k.a. tight coupling).

But how can we reduce the coupling between those entities? Check out the following code:

We improved the previous example by changing the concrete type Cat to an abstract type Animal by using a protocol. Now, instead of tying Person to Cat directly, the relationship is established by the animal property and Animal can be anything that conforms to that protocol.

But we still have an issue. The class Person isn't fully decoupled from Cat. Although, we defined that dependency as a protocol, we are stating what the dependency is: var animal: Animal = Cat()

Person has this abstract dependency, but it's injecting itself its dependency. animal could be any thing that conforms to the Animal protocol. But instead, it is still a Cat.

2. Dependency Injection

So what is dependency injection?

In software engineering, dependency injection is a technique whereby one object supplies the dependencies of another object

In order to completely remove this direct dependency between Person and Cat classes, we can create a separate layer in our application, responsible for resolving abstract types into its concrete instances, this is known as Dependency Injection (DI). This layer will inject the dependencies in the objects as needed and it can be a simple mechanism or really fancy one depending on our needs.

From the Person's perspective, what it only knows is Animal, there's no way to infer what's the concrete dependency. We could do the same for the other side of the relation creating a protocol called PetOwner. Then, from Cat's perspective, it would only knows PetOwner, that could be anything that implements that protocol:

The most common ways to inject the dependencies are Constructor Injection and Setter Injection.

2.1. Constructor Injection

The dependencies are provided through a class constructor. Check out the snippet below:

2.2. Setter Injection

The client exposes a setter method that the injector uses to inject the dependency.

3. Benefits from using dependency injection

3.1. Reusability

This technique forces you to write more decoupled code, so a piece of code depends less on other pieces of code, and because of that it makes easier to reuse code under different contexts.
Checkout the 2 snippets above. Remember we were using Person and Cat, because of the simplicity, but they could be any kind of software modules, even two complex frameworks interacting with each other. Depending on the level of dependency between Person and Cat, it could require a huge refactoring in order to support Dog. And maybe it would be easier to just create another class like Person, duplicate part of the code, so we can have it integrated with Dog.

Aren't convinced yet? Imagine this is an UIViewController subclass and there's a loooot of code there 💔. For instance, you added some blocks of code that retrieves data from a web service. Later you noticed, your app will have another screen almost similar to that first one, but it wants now to get the data from a local database like Core Data.

You check you huge class and notice that it deeply depends on REST calls to the web services:

You don’t know how to make Core Data fit here

If you make changes to support both at the same time, that monster class will become even worse and it would take a lot of time writing/testing it to make sure it won't break on any scenario:

OMG what you are doing

Then you just decide to write a similar subclass of UIViewController, duplicating some code, but reducing the effort of refactoring that class and breaking unexpected things:

Easier approach, but no reuse

3.2. Maintainability

By duplicating code in the example above, you also create other problems: You have to maintain both places up to date, if there’s a bug you need to fix both places. The effort on maintenance increases and you should expect to eventually be surprised by things not working as intended.

If you moved the code that retrieves the data to separated classes, and add them as a dependency to be injected:

  • This would force you to write a clearer API, not being explicit on the real data source (Network, Local Database, …)
  • You would probably start writing your app with support to network only, by injecting the network requests capability, but you eventually could add support to other data sources, without any changes to the class using it, as it’s completely decoupled.
Injecting the dependency

DI in general makes easier to maintain the code, as the modules depends less on the others, you can make changes expecting that any side effect is almost fully restricted to that module.

3.3. Scalability

No surprise here. The loose coupling also helps in the scalability of the code. Think again in the snippets of code above. The Person class was written considering a more generic API called Animal, and initially supported only Cat. In the future, Dog support was introduced, and this still worked without any changes to the Person class.

3.4. Testability

That's probably my favorite part, DI allows us to test better our code. I think in general, iOS developers are afraid to write tests, mostly because they don't even know where to start.

On iOS we have basically two types of tests: Unit Tests and UI Tests.

The first one is used to test a unit of code. This could be anything. A unit could be a function. It could be an entire class. On Unit Tests, you have access to the code you wrote and you can test as you prefer: Create instances of classes, call functions, make assertions on values and verify if the value you found matches the value you expect.

On the other hand, on UI Tests you can't access your code directly, but you can test your app as your testers would do: Tap on buttons, scroll the screen, look to UI elements by its accessibility identifier and also create assertions to verify values on XCUIElements.

Both are really powerful and you should probably use them together to test a lot of things. But usually, people don't know where to start writing the Unit Tests and end up by adding UI Tests to check their code. Why?

Well, Unit Tests are really good to check the pieces of code separately and UI Tests are awesome to check the whole thing integrated. When you write code with tight coupling it's hard to check the code isolated. You want to test a specific class, but this class depends on other class, that depends on other class and in the end of the day, you wished to test an small class and ended frustrated trying to test half of your app. Then you see the UI Tests and it's easier to test a complex flow through the app, because it has everything integrated and you test as a tester would test.

And okay, there's no problem with that, having some UI Tests is better than having no tests at all, and Apple seems to be improving the UI Tests API every year. But, by choosing to not use the Unit Tests you are missing a huge opportunity of testing pieces of your code in detail.

Here shines the DI: You force to write the pieces of your code decoupled, and it make easier to test them separately.
You may think: Okay, but I still need that dependency set in order to test that unit of my code. Don't worry, DI also helps you to mock the code is hard to test:
1. When running your app your data dependency was resolved to a class that retrieves data from a web service? On your test it could resolve to a class that returns mocked local data. You don't need to change a line of code, because they are already decoupled and you benefit from a more predictable and fast test.
2. You want to test a business rule, but there's some UI code bothering you? No worries, mock that view to a class that helps you to better test the business rule.
3. Maybe you want to test a view in particular, check if a long text fits in the space, if there's no overlapping element, if the constraints works in multiple devices/orientations. But your view code depends too much on the business rules. What to do? On the test you could change any business rules dependencies. Let's say you have a view model attached to a view. On the test mock the view model to something that helps you to test your UI.

Obs.: This may seem too abstract now, but I want to make sure we cover all these aspects, when we get to a real app implementation, we will improve a simple app and see the benefits in practice. Again, let me surprise you on how many tests we can extract from a simple example :-)

4. Swinject: Framework for Dependency Injection

As I said earlier, the mechanism that handles the DI on your app can be a simple (built by yourself) or really fancy one depending your needs. There are some third party code developed to make it easier and also add some cool capabilities. I want to cover this one called Swinject.

As stated in its GitHub:

Swinject is a lightweight dependency injection framework for Swift. Dependency injection (DI) is a software design pattern that implements Inversion of Control (IoC) for resolving dependencies. In the pattern, Swinject helps your app split into loosely-coupled components, which can be developed, tested and maintained more easily. Swinject is powered by the Swift generic type system and first class functions to define dependencies of your app simply and fluently.

The following code helps to understand how Swinject works:

The Container is a class responsible to keep the registrations, of type resolutions. In the example above, the abstract type Animal was mapped to the concrete instance of a Cat. The abstract type PetOwner was mapped to the concrete instance of a Person. But if you take a look, to create the instance of a person you also needs to resolve the dependency of the Animal. This is simple but powerful. With more classes the container can become a huge dependency graph and you should ensure all types can be resolved.

Person and Cat decoupled. Dependency graph resolution inside the container

4.1. Cyclic Dependency

The example above is a little bit simplified than our first example. On this example PetOwner needs Animal to be resolved, but Animal can be resolved by itself.
On our initial examples, PetOwner would depend on Animal to be resolved and Animal would depend on PetOwner to be resolved, creating a cyclic dependency. How can we resolve that? Swinject has a mechanism for that. Checkout the following code:

With that technique, PetOwner can be resolved to a Person with a Cat, and then the Animal can be resolved to a Cat with a Person 🙌

Cyclic dependency

4.2. Handling different containers

Notice how powerful the Swinject concept is. By tying the type resolution to a Container instance you create, it's possible to have different dependency graphs for different scenarios.

How can this be useful? Well, let's start thinking you may want a certain type resolution in your production code. You can create a "defaultContainer" or any other name you like. You then use in your app to set the dependencies for your classes.

Imagine now, you want to start creating unit tests for your app, and there some of the classes need to be mocked to make your tests easier, predictable and fast. You can create a separate container with a different resolution graph, ("unitTestContainer", maybe?), that is basically almost the same as "defaultContainer", but resolves some types to mocked networking data instead of real networking data.

And you basically didn't add any complexity to you code. They are still loose coupled and you don't have any complex logic to set the types, you just handle different containers for that. The type resolution auto documents that for you.

Different type resolutions using different containers

4.3. Fancy container ideas

Later we can start trying different approaches and measuring what works and what doesn't. Some ideas:

  • A/B tests, to test different behaviors with different groups of users. We can have a "behaviorAContainer" and a "behaviorBContainer" each one mapping some types to different classes introducing different behaviors, depending on the group of the users
  • Can it be useful with Navigation? Let's say we have this flow of screens in the app: -> screenA -> screenB -> screenC. We may want to keep the destinations as dependencies in a "normalFlowContainer". But maybe the app can open from a push notification, and it may lead to this flow of screens in the app -> screenB -> screenD -> screenE. In this case we could create something like a "pushNotificationFlowContainer".

Obs.: These ideias might seem crazy, don't blame me yet, I didn't test the feasibility, it's just a brainstorming :-)

4.4. Swinject Extensions

Swinject has some side projects that extends its basic functionality:

This introduce some cool ideas. SwinjectStoryboard for instance is really fantastic for people that likes to use Storyboard. When using storyboards, is really hard to do dependency injection as you don't handle the initialization of the view controllers. With SwinjectStoryboard magic (a.k.a. method swizzling) you can without any effort, intercept all view controller initialization, when using storyboards. It's basically a "stop to inject dependencies" which is amazing. That's how it would looks like:

5. TL;DR

This first part introduced the concept of Coupling: the degree of interdependency between software modules. Tigh coupling is usually bad and Loose coupling can help you to have better code by improving: Reusability, Maintainability, Scalability and Testability.

I introduced the concept of Dependency Injection (DI) as a way to achieve loose coupling by transforming fixed and concrete dependencies into flexible and abstract dependencies that must be resolved externally in a separate layer of the application.

I also introduced Swinject, a really cool framework to deal with DI on iOS, also introducing powerful concepts like, resolution of cyclic dependencies and containers.

On the other parts of the article, we will get our hands dirty. As promised, I will introduce a simple example of app built without architecture concerns and we will discuss how we can improve its architecture (dependency injection included). If everything goes well we should see in practice most of the benefits we discussed here.

Feel free to leave some feedbacks, I will try to improve the articles based on them. See you there.

Links to all parts:
Part 1: https://medium.com/@fernandodelrio/dependency-injection-on-ios-part-1-4-8847f302b3d9
Part 2: https://medium.com/@fernandodelrio/dependency-injection-on-ios-part-2-4-359fe6800e90
Part 3: https://medium.com/@fernandodelrio/dependency-injection-on-ios-part-3-4-e85fe7e20de6
Part 4: https://medium.com/@fernandodelrio/dependency-injection-on-ios-part-4-4-ce3723d819d

--

--