iOS: Principles of VIPER
I know, I have already written several articles regarding VIPER and other clean architectures, including which one is the best that fits your project, but since my first article was written 3 years ago, with all the grown knowledge I got across these years, it's time to write in deep details about all the aspects of VIPER that I have to provide you. Said that, let's start.
But we have already MVVM!
It's a fact that MVVM is a great solution for project's scalability, reusability, readability and testable purposes, but there are some reasons why it may not be enough depending on the project.
The first thing I would like to highlight here and most people are not going to agree with is that MVC and MVVM are not exactly architectures, but design patterns. The VIPER architecture actually englobes all of the MVVM concepts and only add some new aspects regarding the SOLID principles. Here is what MVVM truly is:
If you read my very first article, I may have said something like the Model layer is just some fixed place where we can place some business logic and it shall communicate with the ViewModel in order to trigger some updates in our presentation layer. Fact is that
Model is much more abstract than I thought and it definitely is not just a class, but a concept. We may rely our
Model "layer" in a set of business classes or some
Calculator structs that do specific tasks to us. The
Model can even be a service where we can retrieve/update data. The idea of the Model is that it is some place else where the
ViewModel may rely its inputs and outputs.
And VIPER has exactly everything MVVM does, but it describes deeply what the Model is by creating more fixed layers. In generic MVVM, Model classes may have a lot different names you might be familiar with when you worked on some project: Calculators, Parsers, Service, Repository, Facade, Domain and so on. That's the core difference between architectures and design patterns: Architectures have fixed layers with specific names and a single purpose, design patterns may solve problems in different scenes and may not be present in every place, only when necessary.
Enough talking, let's take a look at our major template:
VIPER layers definitions
Firstly, I want to be very clear that VIPER's nomenclature stands from View-Interactor-Presenter-Entity-Router, which are exactly its fixed layers names.
Entity rectangle is a bit smaller than the others because, surprisingly, it's not a standard layer as the others, although other people may think I am wrong.
Let me introduce the definition regarding each one:
- View: It consists of our UI layer, UIViews and UIViewController subclasses establishing some hierarchy. No logic(conditional statements, loops, parsing) should be kept in there. All the outputs come from our ViewModel, oops! They come from our
- Presenter: It may correspond exactly to what the
ViewModeldoes in the MVVM: presentation logic. Everything should be placed in there, deciding which is the context of our screen, what important data should be presented and the content should be kept in a UI-friendly language that is ready to fill our Views, without any parsing. Besides,
Presentermust answer to all user interactions and decide when it's time to move to another scene and trigger the
Router. The business rules that used to be implemented by our
ViewModelin MVVM now moved to the
There is a discussing about the
Presenter being equivalent to MVVM's ViewModel or MVP's Presenter. The answer is: both. It's up to you, if you want more reactive data to fill our UI on demand, implementing as an MVVM
ViewModel is the best choice. If you want this layer to tell our View every time an update is needed, MVP's Presenter is the way.
3. Interactor: This layer corresponds to our use cases within the screen. Basically, every time we want to execute some logic inside our business domain, we should trigger some method that does that in our
Interactor and returns some output. This class is also responsible of being a hub to data sources like API calls and data persistence.
4. Entity: I don't like to call that a layer because there is actually no abstraction inside an entity. Remember Entity-Relation diagrams from Peter Chen you learned in your Data Modeling course? It is just pure entities. It's just a struct/enum describing some entity inside our domain. No logic is needed, and usually they implement the
5. Router: It's basically the class that is responsible for the
Coordinator design pattern, same meaning. It should keep a reference to our
UINavigationController (or the viewController itself) in order to perform push, pop and present operations and do a change of context. Besides, some people like to inject the navigation into our
Presenter instead of being a property. Most companies like to call this layer as
Wireframe , personally I prefer
Router , more declarative.
For short, our procedure is that
View sends user interactions to our
Presenter, which may or not trigger some use cases in the
Interactor or just change or presentation rules without any business logic. The
Interactorperforms some use case operations relying on entities and send back the results to the
Presenter, which change the presentation layers and force an update in our View.
Communication between the layers
Most people defend delegation as the exclusive way to establish communication between the layers, but I strongly disagree. You can perfectly define closures to create a callback to handle the outputs. You can rely on reactive approaches to handle each outcome such as RxSwift, Reactive Swift and Combine, which I prefer if I am working at a declarative project. The important thing to mention is that everything, except by the View, should be unit testable. All upper layers must be representable as a mock and the lower levels as a Spy. For that, each type shall have an interface(protocol)
For me, Combine is the most suitable protocol of communication for VIPER, mainly if you adopt SwiftUI as your interface.
Enough talking, let's code. The project we are about to implement doesn't truly rely on any API or external source in order to decrease complexity. Instead, we are creating our data models through some mocks and using that to fill our interface. This project is a simple contact list with three major use cases that we shall cover during this tutorial:
- Fetch contacts
- Search for a contact
- Fetch contact details
We are about to fetch a list of contacts with just three informations:
This is the result we are expecting:
There are two scenes that hold VIPER layers: Contact List and Contact Details, which may also present an error view. We will cover each of them layer by layer, starting by the Interactor:
It takes care of business rules. All the logical operations regarding data models and API calls triggering should be handled here. The most important aspect about the Interactor is that it doesn't know anything about the screen, UI and UIKit at all. Only logical operations happen here and it should only send the outputs to the
There are two use cases that may occur in our contact list: fetch the items and filter an item. Each of them brings some output to be reflected in our UI. Check the code:
As you can see above, we are defining two different protocols, one for the
Interactor input, simply consisting of our use cases and the other to send the outputs, which shall be our lower layer, the
Now, let's implement our business rules! First of all, we rely on some data model to represent our contact in the scope of this screen:
This is our entity regarding the contact list logic. You can see we predefined some values that shall fill our list. Basically, the
Interactor is responsible for providing the data in order to have our entities, may it come from an API, a database, local persistence, mocks or whatever. In this case, aiming a decrease of complexity, we are instantiating the values.
Now we can implement our concrete
We implemented both use cases:
- We assign our
ContactListEntitystub to our
contactsmemory and them we send it backwards to our
- We take a prefix and search for all the contacts in our entity memory that match. Then, we send it backwards in the same way.
Cool, these are our use cases for the contact list.
2 . Presenter
The Presenter is the layer responsible for parsing our use cases outputs into a suitable format in order to fill our UI. Basically it deals with string formatting, UI event handling, saves constants and knows whenever it's time for a change of scene. It's the central layer, so I would say it's the screen's core.
Talking about our contact list scene, it's responsible for:
- Presenting our items list in a UI friendly way
- Listen to UI lifecycle and interaction events
- Decide when to show the details of a contact
Differently from our
Interactor , the
Presenter is allowed to import UIKit. Besides, the
Presenter shall implement the
ContactListInteractorOutput protocol which the
Interactor sees the
There are two protocols defining our
Presenter contract: an input protocol defining the possible events that are triggered by the
View and another to trigger an update operation in our
View . Since the
View doesn't have any kind of logic(a.k.a if then else and loops), the
Presenter deals with all of it regarding our UI.
Presenter converts our entities into a UI friendly format, there is another type that I should mention. The
ViewModel , differently from it's MVVM meaning, is kind of an entity that may fill our UI:
It's the same of our
ContactListEntity , but now we see our image as an
UIImage instead of a pure string. This is the type that our UI is prepared to receive and consume.
Now let's implement our
As you can see, the
Presenter has a reference to each of the other layers:
Router . It fetches the contacts from the
Interactorwhen receives a
viewWillAppear event from the
View and searches from some contacts when the user types in a search bar. Besides, it works as a delegate/datasource to the tableView, being responsible for defining its structure and content.
It's responsible of defining what is the content of our tableView and handling its interactions. Each cell is created according to our viewModel for that index. When a cell is selected, we trigger our
Router in order to send data to the next scene.
Our Presenter receives and handle our use cases outputs from the Interactor:
There are different literatures discussing if
Presenter is the appropriate place for implementing tableView protocols since it may also be done in the
View . In my opinion, it may trigger some logic and verification about which cell type should be instantiated and this way
Presenter is the proper place.
You may also found the
Presenter familiar with
ViewModel from MVVM, but that is the idea. It listens to all events in our
View and generates content ready to fill our screen. This way, the only event that our
Presenter sends to
updateUI , which makes our
ViewController pick the content hold by
Presenter and update the screen.
View is the most dumb layer of our architecture. It consists of our pair ViewController/View and is responsible for handling our ViewController lifecycle and filling our UI with
Presenter content already generated by our presentation logic. It doesn't contain any logic, no conditionals nor loops:
As you can see, it only builds our interface, creates hierarchy, set up constraints, handles lifecycle events and fills content. Besides, it implements components delegates and
When we are told to update UI, we reload data at our tableView. When our search bar changes its content, we should tell our
4 . Router
The router is responsible for changing our context. It should only have access to the viewController in order to push a new one:
As you can see, we have a protocol which our
Presenter can see and an enum defining which is our new context with our input data as associated type. We may cover the Factory type later, but it's important to know the
ContactDetailsFactory build method returns a view controller.
Now that we implemented our first scene for the contact list, let's conclude our project with our details screen, which is displayed just after selecting a cell from the list. It should contain all the other layers we just discussed, except by the
Router , and we will talk about why.
We just have one use case for our Interactor: fetch details based on the contact name. Since we are mocking everything, we created a Hash structure to return an entity instance for each name:
If we don't get a contact matching the name prefix, it returns an error.
2 . Presenter
Now our second Presenter consists of two details that impact our UI: a details ViewModel, which provides the info to fill our labels and imageView and a boolean indicating if an error happened, and if it's true, we shall make an
ErrorView visible, which is instantiated by our
Presenter as well. The Presenter keeps some String constants defining the text for the error view.
Also, we have a method to handle the
viewWillAppear event. As we previously implemented, the
Presenter output has also an
updateUI method to "redraw" our scene.
We have a ViewModel type which describes our entity in a way it's ready to fill our screen:
The difference here is also in the image that we now represent as an
UIImage instead of a
Now let's implement our Presenter:
As you can see, now our Presenter holds some constant data that shall fill our screen as well as some formats. The class receives as an input a name string from the previous scene and answer some UI events. Everything that dictates our content is safe in the
You also notice that the content our screen is about to display is resumed by our
details viewModel as well as our
hasError boolean, which tells us if we should present an error view. The error view is also instantiated by our Presenter. Both of them have an observer that tells our UI to be updated when value changes. The implementation of
updateUI is responsible of filling the new content. This is triggered when receiving new data from the Interactor use cases:
3 . View
As well as our
ContactListViewController , this View doesn't contain any block of logic, only lifecycle methods and content filling.
Here we also have some UI elements and methods to build our view code interface. As you can see, the
setupContent method is responsible to fill our layout with dynamic data that comes from the Presenter and is called every time we update.
As our details screen doesn't navigate to any other scene, it doesn't require any routing logic, so we are not about to create a Router class for our details scene.
As we have multiple layers, some concepts should be clear when testing each functionality:
- Only logic layers are about to be tested since our View doesn't contain any logic.
- Each suit under test(SUT) shall have a mock in order to avoid other classes to interfere in our testing subjects. It means when we test Interactor, services, core data and other external sources(including feature flags) should be mocked and return crude data. The lower layers that receive the test outputs shall work as a spy which verify if our output is correct.
In a few words, I would say that iOS skills inside corporative environments recommend testing Interactor and Presenter, and Router sometimes.
Not always we rely on business logic that is restrict to a single scene. Because of that, in some companies, VIPER contains a sixth layer we call
Domain . It works as an extra layer that contains business logic that may be reused across different scenes.
Some corporations also like to consider
Service as a VIPER layer due to the fact it builds some API calls based on a scene's context.
This classes are not mandatory to a VIPER project and I will not illustrate them, but it's very important to know these variations.
In this article we gave a deep explanation about the main concepts around VIPER and how to organize each kind of logic inside some screen. We differentiated the ways some companies see our VIPER architecture and how to provide relevant unit tests.
If you wanna check the entire code, this is the repository:
GitHub - pnalvarez/SearchList-VIPER: Simple iOS repository that illustrates the core concepts about…
You can't perform that action at this time. You signed in with another tab or window. You signed out in another tab or…
I hope you enjoyed and keep applying VIPER in your new projects;)
Wanna learn more?
VIPER: Presenter x Interactor core differences