Dependency Injection on iOS — part 3/4

An article about architecture, tests and much more

Fernando del Rio
15 min readNov 26, 2018

Hello again. I started writing a series of articles about Dependency Injection and now it’s time for the part 3. A quick overview for the part 2:

  • We create an simple app called Contact App, that has only 1 screen that loads data from the web (Name, Description and Photo) and displays in the UI
  • The app has a favorite button, for the user interaction:
Contact App
  • We started with a messy architecture with almost no reuse and massive view controllers. Then we improved the app by creating smaller classes with less responsibilities, and by applying concepts like: Protocol Extensions and Child View Controllers. And moving business logic to the model layer:
App after the refactoring

In part 2 we discussed about Reusability, Scalability and Maintainability, and as promised we will talk now about the issues of UIViewController and about Testability.

Summary

1. The UIViewController
1.1. The issues
1.2. Moving the view controller to the view layer
2. MVVM (Model-View-ViewModel)
2.1. Definition
3. Contact App: the next step
3.1. The view controller side
3.2. The view model side
3.3. The result
3.4. Testability improvements, finally
3.5. MVVM considerations
4. Back to Dependency Injection
4.1. Thinking on the tests
4.2. Not that simple to test
4.3. Decoupling our tests
4.4. The architecture
5. TL;DR

1. The UIViewController

We already improved a lot our app with the architectural decisions we made. Although it's easier to reuse, and the classes are more maintainable, we probably still face issues when trying to write some unit tests. To give the next step in the app architecture, we need first to understand why MVC is so criticized on iOS.

1.1. The issues

Well, as we already discussed, the model layer contains what your application is independent of it's presentation. And the view layer contains what your application looks like, in the most pure way, without business rules. The controller should be the bridge between the model and the view, it's the layer responsible to glue everything together.

The issue: on iOS development, we usually create a subclass of UIViewController to represent the controller of our app.

Remember when we discussed the issues with inheritance? You may have a small view controller you just created, with a few lines of code, but it already has a lot of context underneath you are not seeing. It's a subclass of a really complex class, after all.

Also UIViewController is a class that is part of UIKit:

  • It has a lot of view layer responsibilities.
  • It directly interacts with the UIView.
  • We are used to link the IBOutlets to our UI elements inside the view controller.
  • We do animations there.
  • It manages the view life cycle. There are methods that are called for us. It makes sense only with a running app, so not "unit test"-friendly. For instance, We shouldn't be calling a viewDidLoad ourselves. If so, you may get into philosophical questions like: "How valid this test is"?
  • If using storyboards, we don't even take care of the initialization of the controller object.

How are we supposed to test something like that?

1.2. Moving the UIViewController to the view layer

We discussed in the last article that we want to move logic out of the view controller, in order to reduce the amount of responsibilities it handles. But, now what we need to do considering we have something acting as the controller with presentation logic and also acting as the view itself?

Some architecture patterns solve this issue by moving the UIViewController to the view layer definitively. Then we would need another layer between to glue the model layer into the view layer (UIView, UIViewController, Xibs, Storyboards).

This doesn’t mean the UIViewController needs to handle everything in the view side. Actually, it may be good to break things there if the view controller becomes too complex.

In addition to the techniques we discussed earlier (protocol extensions, child view controllers), maybe you would want to move some things inside to an UIView subclass.

For instance, you may have an UIView holding all the IBOutlets, setting the initial state of the view, doing animations. Leaving the view controller free to handle the view life cycle, receive the user actions, etc.

2. MVVM (Model-View-ViewModel)

2.1. Definition

MVVM it's a famous architectural pattern used in iOS, known by allowing a good testability. It consider UIView and UIViewController as being part of the View layer. It features a new layer called view model to be the mediator between view and model.
That's how it looks like:

MVVM

2.2. Data binds

Usually the view model presents data in the view in a passive way, by using data bindings between properties in the view model and the view's UI elements.

By choosing an approach like that, the classes in the view side would need to be as dumb as possible:
The view says how the data will be present.
The view model say what needs to be presented.

3. Contact App: the next step

Okay we didn't stick to a particular architecture on our Contact app, until now. For this point and forward we will be trying MVVM on the app and seeing how it looks like, if it fits our needs.

3.1. The view controller side

As I said before. By moving the view controller to the view layer, it should become really smaller, as it does anything. Check out how it looks like:

Seriously? That's it? That's almost it. In the code above, the view model would be nil. Also we see some initial state of the view being set up, but the first child view controller should also be nil.
We still need to do changes (changes that involves dependency injection ❤️), but we'll get there.

3.2. The view model side

You may also be asking: but how the viewModel will trigger the initial logic, that should happen when viewDidLoad is called?
As it's possible to bind the UILabels, UImageView, we could also do something similar, listening to the trigger of the method viewDidLoad.

So, in practice we will bind the UI elements to some property inside the viewModel and we also will bind the viewDidLoad method to some method inside our viewModel. That's how it looks like:

In the snippet the provider property is nil, we will do some changes later in order to make that work.

Obs.: Notice, as we are still using storyboards, we need also to wait to make the bind of the UI elements (the variables will be nil, until viewDidLoad gets called). That's why we made that additional bind on the viewDidLoad trigger.

3.3. The result

Now we moved almost all logic outside the contact view controller, we should do the same with favorite view controller (for more details, check the details in the GitHub repository).

eIn the end, that's how our layers are looking after the change:

Contact app with MVVM

Really cool. We now have even smaller classes each one with its well defined responsibilities.

3.4. Testability improvements, finally…

With this approach the most important pieces of our software (that we want to create unit tests) are UI independent. The view models and the classes that belong to the model layer, should be more predictable and easy to obtain a good test coverage.

In other hand, the classes that belong to the view layer should have almost no logic. We can focus on testing them as user interfaces without business rules. We could test how they look with dumb data. The text will broke? Are the constraints working fine in multiple devices? We can setup a snapshot test to validate if the UI broke by using image comparison.

We still can run a integration test to understand if the app is working fine with all the pieces in place, but it’s awesome to know that we could test each piece separately by using unit tests.

3.5. MVVM considerations

You may be asking: But, shouldn’t you be binding the show/hide loading to something in the view model? Shouldn’t we be using Rx everywhere, like in the network calls or something?

First of all, yes it would be possible some approach that consider the loading as something triggered via the bindings, but we don’t really need to constrain ourselves on every aspect just to be adopt MVVM.

You may want to introduce this architectural pattern on some parts of your existing projects. This doesn’t mean you need to refactor every singles aspect of them in order to make it work. The show/hide loading we built is serving us well as is. The view model has visibility of the view. It can invoke things on the view side. So this fits our needs.

Also, I don’t believe we really need to force Rx everywhere, as I said before all the context and details matter when choosing these architectural decisions. Maybe you or your team isn’t familiar enough with Rx concepts. Can we use Rx Cocoa to bind a table view to a data source in a more magical way? Of course we can. But this doesn’t make the regular approach impracticable. It’s possible to start doing MVVM without any third party library if this is an easier approach for adoption in your existing project for instance. Past some time, when more maturity comes you can start to test more and more features like. Start trying Rx Cocoa data bindings, later you can try to use more reactive programming concepts and see what works and what doesn’t.

In the end of the day, if it fits your needs, if your code is better to maintain and test, then you have some good indicators, that you are going the right way.

An additional, special notice: You are probably seeing that MVVM fits very well in this simple sample, but it may frustrate you on some other sample. The thing is: there's no silver bullet.

MVVM it's an architecture that tries to solve an specific issue on the view side of the iOS application. We still have that model layer that could be anything. The anything could be so complex it may need an special architecture to handle that. Also, maybe not everything will fit on one of these three layers.

Take navigation for instance. It's not something you would put on the view layer, but it manages views. It probably won't fit the model because it's too UI dependent. It can't fit view model. It's not related to a view in special. It's something that makes the transition between two views and it can be as complex as you need.

You can have different flows depending if it the user opens the app by the home screen or if the user opens via push notification or URL scheme. You need to handle information passing, you need to handle animation.

You may implement that as a default segue in a storyboard, you may create a custom class of UIStoryboardSegue, you may override prepare for segue, and handle the transition inside the view controller. You may try some patterns like Coordinators to manage everything.

You also will probably notice, storyboard is a bad thing once your app gets more complex and needs a more powerful navigation.

It's an important iOS architectural concern and you shouldn't be trying to bend it to fit the pattern you chose. You should be checking how the community is handling such thing and focusing in your problem instead of trying to fit some architecture.

4. Back to Dependency Injection

Okay, so we have a really cool architecture we built from scratch and we ended noticing that MVVM would fit our needs for this particular app. Also we know MVVM should be really easier to test, as it removes almost all logic from UIViewController.

Is that true? Let's see.

4.1. Thinking on the tests

Let's start to think in the tests we can create. We will use the view model as the start point. We want to test our business rules, so we can leave the view layer aside for now.

Let's start with the load method on our Contact View Model. What's its presentation logic?

  • It shows the loading on the view.
  • It asks the model for a contact with its photo.
  • In case of success: it sets the properties name, description and photo (the data bind will ensure this appear in the UI).
  • Also, in case of success: it shows the favorite button
  • In case of success or failure: it hides the loading on the view.

Pretty simple right? But even being simple, it can have many possible scenarios happening:

  • Both requests can return with success.
  • The contact request can succeed but the image request can fail
  • The contact request can fail
  • The model logic is abstract to the view model. The contact provider can forget to call the completion handler on some of the scenarios above.

What possible tests we can think with that in mind:

  • Test that when everything succeed, the data is set in the UI
  • Test that when 1 or both requests fail the data is not set in the UI
  • Test that the loading will be hidden in the end, no matter what

4.2. Not that simple to test

Let's say we want to create some tests for the scenarios above. Is that simple? First, what make a test suite good? A test suite needs:

  • To be as fast as possible
  • To be predictable
  • To test useful things
  • To have a good test coverage

Even if a test is kinda fast, we need to consider:

  • We have many tests.
  • We may want to run tests in different devices,
  • In different iOS versions.
  • We may want to different screen orientations.
  • We may be supporting multiple app versions that needs to be tested. Also, we may need to deploy multiple builds in the day what requires the tests running multiple times.

Saving as much time as possible is indispensable.

The tests needs to be predictable. Let's say we are building an Weather app. We don't want to write a test that checks if it's a sunny day and the next day see the test failing because it rained.

We want good test coverage, too understand we are trying to test as many scenarios as possible, but we also want useful tests that really tells us things are breaking.

With all that in mind, our app as is, does not support good tests:

Let's consider first the part of our app that makes network calls. It isn't fast, it depends on the server response. It's not predictable, the test can fail if the server is down.

Also, we probably won't have a good test coverage: we simply can't test some failing scenarios: It's not feasible to turn down a server during a test, and how reliable would that be?

Beyond that, let's talk about the test that check if the loading is hidden in the end, no matter what. Is that simple to test?

Currently our Loadable API, has a showLoading and a hideLoading method. It does not provide an property to check if the loading is being displayed.

Of course, this is an API we wrote, but imagine this is a third party API, how could we possibly check that? This is something to tied to the UI, but it's impacting our test to assert that on the view model side.

On the favorite side it's also possible to see something hard to test. We may want to check the button's image. We moved the favorite control to the view model. The view model controls the flag that knows if it's the button is favorited or not.

What if we want to check if the button in the view layer is corresponding the flag in the view model layer. We would probably need to check if the button image is set properly. But it's not that cool to do image comparison, to verify that. Our Favorite view controller doesn't expose anything better for us to do this validation.

What about the view testing? We may want to check how things are going on on the view side. Currently, our views have a view models set that makes all the magic. But to better test our views, we want to be able to do things differently. We may want to input dummy data, test different orientations. We don't want to add complexity into the view model to make this work.

MVVM shouldn't be easier to test? What is wrong with the architecture? There's nothing wrong, we are having a bad time testing because our components have a tight coupling between them.

We want to test a part of our software separately, but it depends on other parts that is not that easy to test. You know what we need to do. Let's use Dependency Injection to improve our app and allow us to test things separately without adding complexity to the software.

Once we have everything set up, it should be easy to mock the network by resolving the type to a different class, where it's possible to return in-memory data. This would also enable the capability of failing some request.

For the view model issues, it should be possible to mock the view controller into something that allows us to test better the loading behavior and the favorite button status. We won't be testing the view side, but the view model logic will be covered.

For the view issues it should be possible to mock the view model and make easy to test it without business rules.

4.3. Decoupling our components

In order to apply the concepts of dependency injection, we need to:

  • Transform the concrete dependencies into abstract dependencies
  • Move the injection outside of the components (the component should not declare its dependency itself)

Obs.: I chose to no think to much on the better names for the protocols, just put the suffix -able on everything.

Let's see how the code for the protocols looks like:

Notice that we're exposing only the API that matters to be accessed by the other components. ContactViewable requires the Loadable protocol as we want to be able to call show/hide loading.

Also, notice that we choose Providable instead of NetworkProvidable. Its API is also more generic, it doesn't seem to fit a network class directly. That's on purpose, we may reuse that in future to add support to other data sources, for instance.

You may also notice there are some methods/properties being exposed, that we probably won't use in the code now (like the tapFavorite in the FavoriteViewModelable). There's no need to expose that as it's only called internally. We're exposing it because, we will use that when we start to write some unit tests.

With all the protocols created, we need to first remove all connections between the components leaving them fully decoupled. To inject the dependencies, we will try Swinject:

With this approach we are mapping our protocols to their concrete instance with all dependency graph being set inside the default container. To automatically inject that, we could use a solution like SwinjectStoryboard (considering we're using storyboards). But we don't need that. For now, let's just start the process in the app delegate:

All set. The app should start to work again, without any surprises. Want to try unit tests?
We could create another container resolving things differently like that:

For the view testing we could do something like:

Pretty cool right? Just a different resolution of types, takes care of that. These mocks classes and the mock container could live in the test target, doesn’t need to be part of your production code. There’s no additional complexity into your code 🔥

4.4. The architecture

Now, let's take a look on our architecture diagrams considering the dependency injection:

app target: default container
test target: mock container
test target: snapshot container

Please take a time to check all the code in detail. The code for the app changes to use MVVM and Dependency Injection is available on Github in the following link:
https://github.com/fernandodelrio/dependency-injection-article
The relevant code is in the folder:
3. MVVM + Dependency Injection

5. TL;DR

We're finishing the third part of this series of articles. This part introduced the issues of the UIViewController, how it takes responsibilities from the view layer along with his responsibilities in the controller layer making it hard to test your code.

We introduced the MVVM architectural pattern and applied it in our Contact app in order to improved our concerns with UIViewController, moving it definitively to the view layer.

We discussed a little bit about tests and we understood that, the MVVM itself won't solve all the issues, if there is a lot of tight coupling on our components.

Finally, we applied the concepts of Dependency Injection, using Swinject library. The components are now loose coupled allowing us to better test our code.

This series are almost over, in the part 4 we will try to test our code in the many ways we can figure out and we will check for code coverage as well. You don't want to lose that ;)

Again, 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

--

--