Battle of the iOS Architecture Patterns: View Interactor Presenter (VIP)
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.
This is the final article of the “Architecture Patterns” series and we will going to see how we can implement VIP to the Football Gather application.
If you missed the other articles you can access them below or you can find the links at the end of this article.
- Model View Controller (MVC) — link here
- Model View ViewModel (MVVM) — link here
- Model View Presenter (MVP) — link here
- Model View Presenter with Coordinators (MVP-C) — link here
- View Interactor Presenter Entity Router (VIPER) — link here
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
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.
Cleaning your code like a VIP
VIP is not an architecture pattern which is commonly used. It has been invented by Raymond Law and is intended to be Uncle’s Bob Clean Architecture version applied to iOS projects. More to find here: https://clean-swift.com/.
The main goal of VIP is to fix the massive view controller problem that we have with MVC. VIP also intends to provide a clean alternative to other architecture patterns problems. For example, VIPER has the Presenter at the centre of the app. VIP simplifies the flow by using an unidirectional way of control, becoming easier to invoke methods through layers.
VIP transforms your app control into VIP cycles, providing an unidirectional flow of control.
Example of scenario applying VIP:
- User taps on a button to fetch the list of players. We are in the ViewController.
- The
IBAction
calls a method from the Interactor. - The Interactor transforms the request, performs some business logic (fetches the list of players from the server) and invokes the Presenter to transform the response in a presentable way for the user.
- The Presenter invokes the ViewController to display the players on the screen.
The architecture components are described below.
View/ViewController
Has two functions: sends requests to the Interactor and actions and displays the information coming from the Presenter.
Interactor
The “new” Presenter. This layer is the core of VIP architecture, doing stuff like network calls to fetch data, handle errors, compute entries.
Worker
In Football Gather we use “Services” for a name, but basically these are the same thing. A Worker takes some of the responsibility of Interactors and handles Network calls or database requests.
Presenter
Handles data coming from the Interactor and transforms them into a ViewModel suitable to be displayed in the View.
Router
Has the same role as in VIPER, it takes care of scene transitions.
Models
Similar with other patterns, the Model layer is used to encapsulate data.
Communication
The ViewController communicates with Router and Interactor.
Interactor sends the data to the Presenter. It can have send and receive events from Workers as well.
Presenter transforms the response incoming from the Interactor into a ViewModel and passes it to the View/ViewController.
Advantages
- You no longer have the Massive View Controller problem you have in MVC.
- Using MVVM incorrectly, you might end up having Massive View Models instead.
- Solves the control problem from VIPER with the VIP cycle.
- Using VIPER incorrectly, you can have Massive Presenters.
- The authors say it follows the Clean Architecture principles.
- In case you have complex business logic, it can go into a Worker component.
- Very easy to unit test and use TDD.
- Good modularity.
- Easier to debug.
Disadvantages
- Too many layers and gets boring after a while if you don’t use a code generator.
- You write a lot of code even for simple actions.
- Is not great for small apps.
- Some of the components might be redundant based on your app use cases.
- App startup will slightly increase.
VIP vs VIPER
- In VIP, the Interactor is now the layer that interacts with the View Controller.
- The ViewController holds a reference to the Router in VIP.
- If used incorrectly, VIPER can grow massive Presenters.
- VIP has an unidirectional flow of control.
- Services are called Workers in VIP.
Applying to our code
Transforming the app from VIPER to VIP might not be as easy as you may think. We can start with transforming our Presenter into an Interactor. Next, we can extract the Router from the Presenter and integrate into the ViewController.
We keep the Module assembly logic that we did for VIPER.
Login
scene
Moving on to our Scenes. Let’s start with the Login scene.
As you can see we no longer tell the Presenter that the view has been loaded. We now make a request to the Interactor to load the credentials.
The IBActions
have been modified as below:
We start the loading view, construct the request to the Interactor containing the username, password contents of the text fields and the state of the UISwitch
for remember the username.
Next, handling the viewDidLoad
UI updates are made through LoginViewConfigurable
protocol:
Finally, when the logic service call has been completed we call from the Presenter the following method:
The Interactor looks the same as the one from the VIPER architecture. It has the same dependencies:
The key thing here is that we now inject the Presenter through the initialiser and it is no longer a weak variable.
Loading credentials is presented below. We first take the incoming request from the ViewController. We create a response for the presenter and call the function presentCredentials(response: response)
.
The login and register methods are the same, the exception being the Network service (Worker).
The Presenter doesn’t hold references to the Router or Interactor. We just keep the dependency of the View, which has to be weak to complete the VIP cycle and not have retain cycles.
The Presenter has been greatly simplified, exposing two methods of the public API:
The Router layer remains the same.
We apply some minor updates to the Module assembly:
PlayerList
scene
Next, we move to PlayerList
scene.
The ViewController
will be transformed in a similar way - the Presenter will be replaced by Interactor and we now hold a reference to the Router.
An interesting aspect in VIP is the fact we can have an array of view models inside the ViewController:
We no longer tell the Presenter that the View has been loaded. The ViewController will configure its UI elements in the initial state.
Similar to Login, the IBActions
will construct a request and will call a method within the Interactor.
When the data will be fetched and ready to be displayable, the Presenter will call the method from the ViewController displayFetchedPlayers
.
The data source of the table view can be seen below:
As you might notice, our cells don’t require a Presenter any more. We have everything needed (array of view models) in our view controller.
The Interactor is detailed below:
The Detail, Add, Confirm screens delegate are now moved from the Presenter to the Interactor:
Finally, the Presenter:
Testing our business logic
Switching from VIPER to VIP when unit testing the Gather
business functionality is not as hard as it may seem.
Basically, the Interactor is the new Presenter.
We follow the same Mock approach, with boolean flags that are set to true whenever a function is called:
Here are some unit tests of the Interactor:
And the Presenter unit tests:
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
We applied VIP to an application written in VIPER and the first thing we might notice is the Presenters are simplified and much cleaner. If we would be coming from an MVC app, the view controllers would be reduced considerably by separation of concerns
VIP simplifies the flow by using an unidirectional way of control, becoming easier to invoke methods through layers.
The average build times are similar with the ones from VIPER and MVP, around 10 seconds.
Having more unit tests, adds on top of the test execution time. However, we were a little faster than VIPER.
The Presenters have been greatly reduced with 514 lines of code compared to VIPER. But the main downside is that we gain the number of lines in the Interactors, overall being increased with 508 lines of code. Basically, what we took out from the Presenters we put in the Interactors.
Personally, I prefer VIPER. In VIP architecture there are things that I don’t like and from my point of view they are not following as much as they brag the Uncle’s Bob principles.
For example, why do we need to construct a Request object, even if there is nothing attached to it? I mean, we could not do that, but if you open up the repo of examples you can see plenty of empty requests objects.
There is a lot of boiler plate code.
Keeping an array of view models inside the ViewController creates complexity and can easily become out of sync with the Worker models.
Of course you can use your own variation of VIP that can mitigate these problems.
On a positive note, I like the concept of VIP cycles and how easy it is to use TDD. However, following a strict rule on the layers, each minor change can be hard to implement. It should be SOFTware, right?!
Useful Links
- The iOS App, Football Gather — GitHub Repo Link
- The web server application made in Vapor — GitHub Repo Link
- Vapor 3 Backend APIs article link
- Migrating to Vapor 4 article link
- Model View Controller (MVC) — GitHub Repo Link and article link
- Model View ViewModel (MVVM) — GitHub Repo Link and article link
- Model View Presenter (MVP) — GitHub Repo link and article link
- Coordinator Pattern — MVP with Coordinators (MVP-C) — GitHub Repo link and article link
- View Interactor Presenter Entity Router (VIPER) — GitHub Repo link and article link
- View Interactor Presenter (VIP) — GitHub Repo link