The Acyclic Dependencies Principle on iOS

Or how to avoid Merging Nightmares and Long Compilation Times

César Vargas Casaseca
Axel Springer Tech

--

More and more we see how the iOS community strives for more modularized architectures. And it is obviously not just a fad. The benefits are clearly evident, the most immediate being much shorter compilation times given that you can just focus on the working framework. Furthermore, you are aiming for a cleaner architecture, one much more readable and testable. On top of that, you can just reuse your component in any other application, or exchange it as you wish.

But same as when creating your object oriented design you rely on the SOLID principles to make it easy to maintain and extend, when constructing your components architecture you always must bear in mind these three points:

  • APD: The Acyclic Dependencies Principle
  • SDP: The Stable Dependencies Principle
  • SAP: The Stable Abstractions Principle

In this first article we will focus on the ADP, The Acyclic Dependencies Principle.

The Acyclic Dependencies Principle

Allow no cycle in the component dependency graph

The theory is simple, our dependency graph should not contain any cycle. If there is one cycle, we are just creating a big monolithic component causing exactly what we wanted to avoid in the first place: longer compilation times and other effects caused by a highly coupled architecture, such as the morning after syndrome. How can we avoid that? Let’s look at an example on iOS with the MVVM architecture.

An Acyclic Dependency Architecture

Let’s suppose that we modularized the architecture of an IOS application that uses MMVM. Model-View-ViewModel (MVVM) is a structural design pattern that separates objects into three distinct groups: Models, Views and ViewModels. Because we are super impressed with the benefits stated above, we are going to create a Framework for each of them.

Our application is simple, the UI will display distinctive elements depending of the User Permissions in the User Profile, and it will be the responsibility of the ViewModel to discern what to show:

This is a directed graph, we can follow the dependencies with the arrows. Moreover, there are no cycles in it, it is a Directed Acyclic Graph (DAG). It is awesome.

Photo by Jon Tyson on Unsplash

Here we have 4 components, obviating the View or some Routing layer that are not relevant for our case. The UserProfileViewModel depends on the User struct in the Model to get the information. This one at the same time depends on the PermissionsManager to enrich its data with its given permissions.

The Misdeed

Now, the Interaction Designer has come to us with a new feature, we will show a totally different view depending on the User Permissions. That way, they will sell more subscriptions because we show more promotional content directed to a non-paying user.

Once they let us know this new task, it is time to design our approach. We will create a Factory class that will provide the right View Model depending on the User Permissions. After some deliberation, the “expert” in our team decides that it should be included in the Permissions Manager framework, given that everything related to the permissions is its responsibility. (please forgive this atrocious architecture).

Alright, so it is! The factory is implemented in the PermissionsManager, and our architecture looks like this now:

We do not have an acyclic graph anymore, there is a cycle between ViewModel, Model, and PermissionsManager. We are doomed! Instead of three components, we have a big one that should be compiled entirely every time we modify any of them. Besides, given the tight coupling, one change might have consequences in any of them. Not good. Actually awful.

Photo by Jasmin Sessler on Unsplash

Break it!

Once we are aware of the dreadful consequences of the last addition, it is time to break the cycle. To accomplish that we have two different strategies:

Apply the Dependency Inversion Principle (DIP)

Depend on abstractions, not concretions, you asshole.

We will create a protocol revealing the functions that provides the Permissions to the User Model. The Permissions Manager will implement that protocol, but will be hidden to the model, thus inverting the dependency between the Model and the PermissionsManager:

It might look odd having a component with only a protocol on it and no executable code, but this is a necessary and common practice in other languages such as Java and C#. Robert C. Martin, Clean Architecture.

Create a component between them.

To break it, we could create a component between the ViewModel and the PermissionsManager. The ViewModelFactory depends on both ViewModel and PermissionsManager, hence breaking the previous cycle:

You can combine both strategies to end up with the cleanest approach, one that ensures you a good maintainability and flexibility when adding new features to the project.

Wrapping it up

Photo by Josh Rakower on Unsplash

Now that modularizing our apps is in vogue and with good reason, we should be aware that before adding new components we have to stop and think. Are we improving our architecture? Are we actually achieving the goals we strive for with our separate modules? Or are we just having the same monolithic structure although with fancier looks? We should be open to this reflection not only at design time, but also when refactoring the current solution if it shows the most common pitfalls of a non-clean architecture.

If you want to know more, I refer you to the bible on these matters, Clean Architecture: A Craftsman’s Guide to Software Structure and Design (Robert C Martin) There you will find more examples about this and the other principles I mentioned above.

And of course, in case you have more questions or suggestions please drop a comment below or send me a message.

Break the cycle, man!

--

--

César Vargas Casaseca
Axel Springer Tech

Senior iOS Developer at Automattic. Allegedly a clean pragmatic one. Yes, sometimes I do backend. Yes, in Kotlin, Javascript or even Swift.