iOS: Principles of VIPER

Pedro Alvarez 🇵🇹🇧🇷
13 min readNov 1, 2022

--

Image from https://wall.alphacoders.com/by_sub_category.php?id=206005&name=Viper+Wallpapers&filter=4K+Ultra+HD

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.

The 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:

  1. 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 .
  2. Presenter: It may correspond exactly to what the ViewModel does 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, Presenter must 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 ViewModel in MVVM now moved to the Interactor .

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 Codable protocol.

Image from https://www.lucidchart.com/pages/er-diagrams

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 ourRouter from 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)

Delegation: each layer has a reference to the next one and the next has a weak reference to the previous
Combine, each layer receives a publisher from its upper one and acts as a subscriber

For me, Combine is the most suitable protocol of communication for VIPER, mainly if you adopt SwiftUI as your interface.

Implementation: Search

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:

  • Image
  • Name
  • Number

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:

1. 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 Presenter .

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 Presenter .

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 Interactor :

We implemented both use cases:

  • We assign our ContactListEntity stub to our contacts memory and them we send it backwards to our Presenter
  • 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 Presenter through.

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.

Since the 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 Presenter :

As you can see, the Presenter has a reference to each of the other layers: View , Interactor and 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 View is updateUI , which makes our ViewController pick the content hold by Presenter and update the screen.

3. View

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 Presenter outputs:

When we are told to update UI, we reload data at our tableView. When our search bar changes its content, we should tell our Presenter .

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.

Contact Details

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.

1. Interactor

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.

Our Presenter protocols

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 String .

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 Presenter .

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.

4. Router(?)

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.

Tests

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.

Other layers?

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.

Conclusion

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:

I hope you enjoyed and keep applying VIPER in your new projects;)

Wanna learn more?

VIPER: Presenter x Interactor core differences

VIPER: Most suitable design patterns

--

--

Pedro Alvarez 🇵🇹🇧🇷

iOS & Android Developer | WWDC19 scholarship winner | Blockchain enthusiast