Before starting to develop an iOS app, we have to think of 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 third article of the series, we will transform the MVVM app in MVP.
As usual, we will see how we apply the pattern to each screen, seeing the actual implementation and the source code. At the end, we will show the build times and detail some key observations about MVP, compared to the other architecture patterns.
If you just want to see the code, feel free to skip this post. The code is available open source on GitHub.
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.
- Add players in the app
- Assign teams to the players
- Edit players
- Set a countdown timer for matches
What is MVP
MVP is slightly the same as MVVM, but with some key notes:
- You now have a presenter layer.
- You can control the view from the presenter layer.
- The Model layer is exactly as in the others, it is used to encapsulate business data.
- Is an interface responsible for domain data.
- When something happens in the view layer, for example when the user initiates an action, it is communicated to the model through the Presenter.
- When the model is changed, for example when new data is made available and we need to update the UI, the Presenter updates the View.
- View layer is the same as in MVVM, but the View now lacks responsibility for updating its state. The presenter owns the View..
- Views can’t communicate directly with the Model, everything is done through the Presenter.
- Responsible for handling the events coming from the view and triggering the appropriate events with the Model.
- Connects the View with the Model, but without any logic added to the View.
- Has a 1:1 mapping to a View.
- Can communicate with both layers, Model and View/View Controller.
- The view updates will be done from the Presenter.
- When data changes, it makes sure those changes are communicated to the user interface, updating the View.
When to use MVP
Use it when you feel MVC and MVVM doesn’t work quite well for your use cases. You want to make your app more modularised and increase your 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.
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
- The separation of layers is better than in the other patterns.
- We can test most of the business logic.
- The assembly problem is revealed more prominently in MVP. Most likely you will have to introduce a Router or a Coordinator to take care of navigation and module assembly.
- There is a risk that your Presenter will turn into a massive class, because he has more responsibilities.
Applying to our code
There are two big steps we need to do:
- Go one by one through the ViewModels and turn them into Presenters
- Separate the View from the ViewController
The applied MVP pattern is detailed below:
Let’s see how the
LoginPresenter looks like:
All parameters will be injected through the initialiser.
The Keychain interactions are defined below:
And we have the two services:
The register function is basically the same as the login one:
LoginView has the following protocols:
Most of the ViewController logic is now inside the View.
Finally, the ViewController:
We take screen by screen, and turn the existing MVVM architecture into an MVP.
Next screen is PlayerList, and we start with the web API calls:
Now, the check for playe deletion is made inside the Presenter and not in the View/ViewController.
If we look in the
PlayerListView, at table view's data source methods, we observe that the Presenter is behaving exactly as a ViewModel:
PlayerListViewController acts now more as a router between Edit, Confirm and Add screens.
Breaking into responsibilities, PlayerList module has the following components.
- Is implementing the
PlayerListTogglableprotocol to go back to the
listViewmode state, whenever a gather is completed (called from
- Holds an
- It sets the presenter, view delegate and tells the view to setup.
- Handles the navigation logic and constructs that models for Edit, Add and Confirm screens.
- Implements the
PlayerListViewDelegateand does the following operations:
- Changes the title when the view requested to change it (
func didRequestToChangeTitle(_ title: String).
- Adds the right navigation bar button item (Select or Cancel selection of players)
- Performs the appropriate segue with the identifier constructed in the Presenter.
- Presents a simple alert with a title and a message (when the service failed, for example)
- Presents the delete confirmation alert.
- By implementing the
PlayerDetailViewControllerDelegate, when a player is edited, it tells the View to refresh.
- Same case for
AddPlayerDelegate, and here, it tells the View to load again the list of players.
- Exposes the public API,
PlayerListViewProtocol. This layer should be as dumb as possible and not do complex things.
- At a first glimpse at
PlayerListPresenterProtocol, we notice it does a lot of things.
- Exposes the needed methods for the View, such as
barButtonItemIsEnabled, and so on.
- We decided to extract the
ViewStateinto a new file, keeping the same functionality that we had in MVVM, using the Factory Method pattern to allocate the different states of
Continuing with PlayerDetail screen, we separate the View from the ViewController.
Following the same pattern, the navigation to edit screen is done through delegation:
- user taps one of the row that corresponds with a player property. The View tells the ViewController that we want to edit that field, the ViewController performs the correct segue. In
prepare:for:seguemethod, we allocate the properties required to edit the player.
PlayerDetailView is presented below:
And the table view delegate and data source implementation:
We follow the same approach for the remaining screens of the app.
Exemplifying below the
PlayerEdit functionality. The
PlayerEditView class is basically the new ViewController.
The selectors are pretty straightforward:
And the Public API:
PlayerEditPresenter handles the business logic and exposes the properties for updating the UI elements.
An API call is detailed below:
PlayerAdd, Confirm and Gather screens follow the same approach.
Testing our business logic
The testing approach is 90% the same as we did for MVVM.
In addition, we need to mock the view and check if the appropriate methods were called. For example, when a service API call is made, check if the view reloaded its state or handled the error in case of failures.
Unit Testing below
Toggle timer becomes more interesting:
And below is the mock view:
I’d say that testing the presenter is very cool. You don’t need to do magic stuff, and the methods are very small in size which is helping.
The complex thing comes with the fact that you will need to mock the View layer and check if some parameters are changing accordingly.
Lines of code — View Controllers
Lines of code — Views
Lines of code — Presenters
Lines of code — Local Models
Tests were run in iPhone 8 Simulator, with iOS 14.3, using Xcode 12.4 and on an i9 MacBook Pro 2019.
The application has now been rewritten from MVVM to MVP. The approach was straightforward, we took ViewModel by ViewModel and replaced it with a Presenter layer.
Additionally, we created a new layer — View — out of the ViewController to better separate the concerns. The code looks cleaner, view controllers are thinner, the classes and functions are small and concentrate doing one thing, embracing the Single Responsibility Principle.
I personally prefer this pattern rather than MVVM when it comes to apps developed with
UIKit in mind. It feels more natural than MVVM.
Taking a look at the key metrics we can make the following notes:
- the View Controllers are much thinner, overall we reduced their size with more than 1,000 lines of code
- however, we introduced a new layer for the UI updates — called the View
- Presenters are bigger than View Models, because they hold an additional responsibility, to manage views
- writing unit tests was similar as per MVVM, obtaining almost the same code coverage, 97.2%
- having more files and classes, we had a small impact on the build time, being increased with 530 ms compared with MVVM and 400 ms from MVC
- surprisingly, the average unit test execution time has been quicker with 1,36 seconds compared with MVVM
- comparing to the MVC pattern, the unit tests covering the business logic were much easier to write
Really cool we saw together how to transform an app written in MVVM into a different pattern, such as MVP. From my point of view, MVP with the separation of View from the ViewController, is much nicer than MVVM. It brings more power to your layers, making them decoupled from each other and is much easier to use dependency injection.
Thanks for staying until the end! As usual, here are some useful links below.
- 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
- View Interactor Presenter Entity Router (VIPER) — GitHub Repo link
- View Interactor Presenter (VIP) — GitHub Repo link