Battle of the iOS Architecture Patterns: View Interactor Presenter Entity Router (VIPER)

Radu Dan
Geek Culture
Published in
11 min readJul 17, 2021
Architecture Series — View Interactor Presenter Entity Router (VIPER)

Motivation

Before starting to develop an iOS app, we have to think about the structure of the project. We need to consider how we add those pieces of code together so they make sense later on — when we come back and revisit a part of the app — and how to form a known “language” with the other developers.

In this implementation article, we will transform our Football Gather app into a VIPER codebase architecture.

If you missed the other articles you can access them below or you can find the links at the end of this article.

Are you impatient and just want to see the code? No worries! You have it available on GitHub.

Following the same approach as we did for the other posts, we will first say a few things about this pattern and why it’s useful. Then we will see the actual implementation.
Finally, we will show some figures about compilation and build times, check how easy was to write unit tests and state our conclusions.

Why an Architecture Pattern for Your iOS App?

The most important thing to consider is to have an app that can be maintainable. You know the view goes there, that this view controller should do X and not Y. And more important, others know that too.

Here are some advantages of choosing a good architecture pattern:

  • Easier to maintain
  • Easier to test the business logic
  • Develop a common language with the other teammates
  • Separate the responsibility of your entities
  • Fewer bugs

Defining the Requirements

Given an iOS application with six or seven screens, we are going to develop it using the most popular architecture patterns from the iOS world: MVC, MVVM, MVP, VIPER, VIP, and Coordinators.

The demo app is called Football Gather and is a simple way for friends to track the scores of their amateur football matches.

Main features

Ability to:

  • Add players in the app
  • Assign teams to the players
  • Edit players
  • Set a countdown timer for matches

Screen mockups

Screen mockups of “Football Gather”, the iOS app

Backend

The app is powered by a web app developed in the Vapor web framework. You can check out the app in my Vapor 3 initial article and the article about Migrating to Vapor 4.

Dude, where’s my VIPER?

VIPER stands for View-Interactor-Presenter-Entity-Router.

We saw in MVP what the Presenter layer is and what it does. This concept applies as well for VIPER, but has been enhanced with a new responsibility, to get data from the Interactor and based on the rules, it will update / configure the View.

View

Must be as dumb as possible. It forwards all events to the Presenter and mostly should do what the Presenter tells it to do, being passive.

Interactor

A new layer has been introduced, and in here we should put everything that has to do with the business rules and logic.

Presenter

Has the responsibility to get data from the Interactor, based on the user’s actions, and then handle the View updates.

Entity

Is the Model layer and is used to encapsulate data.

Router

Holds all navigation logic for our application. It looks more like a Coordinator, without the business logic.

Communication

When something happens in the view layer, for example when the user initiates an action, it is communicated to the Presenter.

The Presenter asks the Interactor for the data needed by the user. The Interactor provides the data.

The Presenter applies the needed UI transformation to display that data.

When the model / data has been changed, the Interactor will inform the Presenter.

The Presenter will configure or refresh the View based on the data it received.

When users navigate through different screens within the app or take a different route that will change the flow, the View will communicate it to the Presenter.

The Presenter will notify the Router to load the new screen or load the new flow (e.g. pushing a new view controller).

Extended VIPER

There are a few concepts that are commonly used with VIPER architecture pattern.

Modules

Is a good idea to separate the VIPER layers creation from the Router and introduce a new handler for module assembly. This is done most likely with a Factory method pattern.

And the concrete implementation for our app:

We will see later more source code.

TDD

This approach does a good job from a Clean Code perspective, and you develop the layers to have a good separation of concerns and follow the SOLID principles better.

So, TDD is easy to achieve using VIPER.

  • The modules are decoupled.
  • There is a clear separation of concerns.
  • The modules are are neat and clean from a coding perspective.

Code generation tool

As we add more modules, flows and functionality to our application, we will discover that we write a lot of code and most of it is repetitive.

There is a good idea to have a code generator tool for your VIPER modules.

Solving the back problem

We saw that when applying the Coordinator pattern we had a problem when navigating back in the stack, to a specific view controller.
In this case, we need to think of a way if in our app we need to go back or send data between different VIPER modules.

This problem can be easily solved with Delegation.

For example:

More practical examples we are going to see in the section Applying to our code.

When to use VIPER

VIPER should be used when you have some knowledge about Swift and iOS programming or you have experienced or more senior developers within your team.

If you are part of a small project, that will not scale, then VIPER might be too much. MVC should work just fine.

Use it when you are more interested in modularising and unit test the app giving you a high code coverage.
Don’t use it when you are a beginner or you don’t have that much experience into iOS development.
Be prepared to write more code.

From my point of view, VIPER is great and I really like how clean the code looks. Is easy to test, my classes are decoupled and the code is indeed SOLID.

For our app, we separated the View layer into two components: ViewController and the actual View.
The ViewController acts as a Coordinator / Router and holds a reference to the view, usually set as an IBOutlet.

Advantages

  • The code is clean, SRP is at its core.
  • Unit tests are easy to write.
  • The code is decoupled.
  • Less bugs, especially if you are using TDD.
  • Very useful for complex projects, where it simplifies the business logic.
  • The modules can be reusable.
  • New features are easy to add.

Disadvantages

  • You may write a lot of boilerplate code.
  • Is not great for small apps.
  • You end up with a big codebase and a lot of classes.
  • Some of the components might be redundant based on your app use cases.
  • App startup will slightly increase.

Applying to our code

There will be major changes to the app by applying VIPER.

We decided to not keep two separate layers for View and ViewController, because one of these layer will become very light and it didn’t serve much purpose.

All coordinators will be removed.

First, we start by creating an AppLoader that will load the first module, Login.

We allocate AppLoader in AppDelegate and call the function build() when the app did finish launching.

We saw earlier, how we use ModuleFactory to create VIPER modules. We provide an interface for all modules that require assemble in our app.

We have a struct ModuleFactory that is the concrete implementation of the above protocol.

Let’s see how LoginModule is created.

Every module will have a function assemble() that is needed when implementing the AppModule protocol.

In here, we create the references between the VIPER layers:

  • We set the view to the presenter (weak link).
  • Presenter holds a strong reference to the Interactor.
  • Presenter holds a strong reference to the Router.
  • Interactor holds a weak reference to the Presenter.
  • Our View holds a strong reference to the Presenter.

We set the weak references to avoid, of course, retain cycles which can cause memory leaks.

Every VIPER module within our app is assembled in the same way.

LoginRouter has a simple job: present the players after the user logged in.

One important aspect that we missed when applying MVP to our code, was that we didn’t made our View passive. The Presenter acted more like a ViewModel in some cases.

Let’s correct that and make the View as passive and dumb as we can.

Another thing that we did, was to split the LoginViewProtocol into multiple small protocols, addressing the specific need:

We combined all of them by using protocol composition and named them with a typealias. We use the same approach for all of our VIPER protocols.

The LoginViewController is described below:

Loadable is the same helper protocol that we used in our previous versions of the codebase. It simply shows and hides a loading view, which comes in handy when doing some Network requests. It has a default implementation for classes of type UIView and UIViewController (example: extension Loadable where Self: UIViewController).

ErrorHandler is a new helper protocol that has one method:

The default implementation uses the static method from AlertHelper to present an alert controller. We use it for displaying the Network errors.

We continue with the Presenter layer below:

We set our dependencies to be injected via the initialiser. Now, the presenter has two new dependencies: Interactor and Router.

After our ViewController finished to load the view, we notify the Presenter. We want to make the View more passive, so we let the Presenter to specify the View how to configure its UI elements with the information that we get from the Interactor:

The service API calls to login and register are similar:

When the API calls are finished, the Interactor calls the following methods from the Presenter:

The Interactor now holds the business logic:

We expose in our Public API the actual values for rememberMe and the username:

The service handlers are lighter than in previous architecture patterns:

When editing a player, we use delegation for refreshing the list of the players from the PlayerList module.

Navigating to Edit screen

We show PlayerDetailsView by calling the router from PlayerListPresenter:

PlayerListRouter is shown below:

Now, we use the same approach from Detail screen to Edit screen:

And the router:

Navigating back to the List screen

When the user confirms the changes to a player, we call our presenter delegate.

The delegate is PlayerDetailsPresenter:

Finally, we call the PlayerDetailDelegate (assigned to PlayerListPresenter) and refresh the list of players:

We follow the same approach for Confirm and Add modules:

The Router class is presented below:

Implementing the service handler in PlayerAddPresenter:

Finally, delegation to the list of players:

In this architecture pattern, we wanted to make the View as passive as we could (this concept should be applied to MVP, too).
For that we created for the table rows, a CellViewPresenter:

The concrete class described below:

The presenter will update the CellView:

In PlayerViewController, we have the cellForRowAt method:

Inside the Presenter we cache the existing cell presenters:

Finally, we present our main app module, Gather.

GatherViewController is simplified and looks great:

We expose the Public API using the protocol GatherViewConfigurable:

GatherViewReloadable defines the reloadData method. Here, we reload all picker components and the tableView data.

We don’t have any more two separate layers, ViewController and View. The alert controller presentation is done inside the View layer:

We could have used a separate layer and create another object for the table’s and picker’s DataSource and Delegate, but for the sake of our exercise we preferred to implement the methods inside our ViewController:

We also have implemented the ScoreStepperDelegate to pass to the presenter the UI updates of the team sliders.

And finally some helper protocols to add functionality for our custom cell, showing and hiding a loading spinner and to handle errors.

Testing our business logic

In VIPER we have the Interactor handling our business logic. This should be tested.

However, the core of the architecture is the Presenter, which handles updates to the View and communicates with both the Router and Interactor. This should be tested as well.

Testing the Presenter

The class for for unit testing the presenter is GatherPresenterTests:

Testing the table view’s and picker view’s data source:

Testing the stepper handler:

Testing the IBActions:

The mocks are defined in a separate file:

Testing the Interactor:

We mock the Presenter:

Key Metrics

Lines of code — Protocols

Lines of code — View Controllers and Views

Lines of code — Modules

Lines of code — Routers

Lines of code — Presenters

Lines of code — Interactors

Lines of code — Local Models

Unit Tests

Build Times

Tests were run on an 8-Core Intel Core i9, MacBook Pro, 2019. Xcode version: 12.5.1. macOS Big Sur.

Conclusion

VIPER is an awesome architecture if you are looking for clean code. We can even go further and create more layers if we truly want to embrace Single Responsibility Principle.

Unit tests are easy to write.

On the other hand, we have a lot of files, protocols and classes in our project. When something changes or we need to update something in the UI, we end up changing a lot of things and this takes time.

Specifically to our application, transforming the existing MVP-C architecture into VIPER was harder to ahieve comparing to the other patterns. We had to merge the View and ViewController layers first and then we had to touch almost all classes and creating new classes / files.

The functions are quite small, most of them concentrating on doing one thing.

The protocols files are useful if we want to use static frameworks, decoupling the modules from the main .xcodeproj.

We notice that the ViewControllers have been reduced considerably, all of them summing up almost 800 lines of code. This is more than double than the ViewControllers from MVC, where we had 1627 lines of code.

On the other hand, we now have new layers:

  • Protocols — this is just the abstractions of the modules, containing just the definition of the layers.
  • Modules — the assemble of the VIPER layers. It’s part of the Router and is usually initialized via a factory.
  • Interactors — contains the business logic and network calls, orchestrating the data changes.

The new layers add 1903 lines of code.

Writing unit tests was very fun to do. We had everything decoupled and was a pleasure to assert different conditions.
We manage to obtain 100% code coverage.

However, the build times are the highest of all. Each time we delete the content of the Derived Data folder and clean the build folder, we loose 10.43 seconds.
This takes almost one second more than the time when the app was in MVVM or MVC. But who knows how much time we saved fixing potential bugs?!

Executing unit tests after cleaning folders, takes close to 20 seconds. We have more tests, 46 in total.

More files, classes and dependencies add more time to the compiler’s build time.

Lucky for us, we don’t have to clean build and wipe out derived data folder each time we want to run the unit tests. We can leave this responsibility to the CI server.

I personally like using VIPER in medium to large applications that don’t tend to change very often and we add new features on top of the existing ones.

However, there are some notable disadvantages when adopting VIPER.
Firstly, you write a lot of code and you might think why you need to go through three layers, instead of just doing it in the View Controller.

Secondly, it doesn’t make sense for small applications, we don’t need to add boilerplate to simple tasks, creating redundant files.

Finally, your app compilation time and even your startup time will increase.

Thanks for staying until the end!

Useful Links

--

--