During the last months, VIPER has become more and more popular. If you are following news and articles about mobile application architecture you would definitely understand the huge movement of software engineers towards MVVM, MVP and VIPER.
In my honest opinion, there is a huge gap between reading for a pattern and actually applying that. Especially with structural patterns as MVC, MVVM, VIPER, you will often catch yourself asking if a certain functionality belongs to the interactor or the presenter.
In this article, we will try to explain the main entities that VIPER consists of, how they are connected and how to use them in a real-life application.
One thing to remember
Before you proceed I would like to clarify something:
There is not a perfect pattern to structure your application.
MVC, MVP, MVVM, VIPER or any other patterns have advantages and disadvantages. Given the fact that this article is focusing on the last one I have to say that VIPER is a very good choice for well designed and extensive projects. Personally, I consider VIPER ideal for big projects with well-defined criteria. The fact that VIPER is verbose and supports multiple layers might make less suitable for smaller projects than some other patterns. On the other hand, if the project is already extensive in use cases and flows, or intends to be, the reusability and testability of VIPER make it if not the best choice at least one of the finalists.
Element Explanation
If you ended up here, you might already have heard that VIPER was named after its main entities:
- View
- Interactor
- Presenter
- Entity
- Router.
If you are not familiar with the responsibilities related to each entity or if you want to refresh your knowledge please continue reading, otherwise feel free to skip this part.
View:
The main responsibility of this object is to host UI elements and capture the UI events. If a UI event is triggered (ex. a tap on a button) the view is responsible to propagate that event to the presenter. The view should not contain any business logic. The view could be either a UIViewController, UIView or even a UIControl if you want to be something complicated.
Long story short: The view should never decide to do something. Any update on the view should be driven by the presenter.
Presenter:
The main responsibility of this class is to interpret UI events propagated from the view and populate the view with data it receives from the Interactor and contain UI related business logic. The interpretation of the events could be either asking the view to perform an action (ex. update the UI) or asking the Router to perform navigation logic. A presenter is dedicated to a view. Thus it is much easier to include inside the presenter the decisions for navigation and UI update. In case of a change in the UI the presenter is easier updated than the ViewController.
Long story short: Inside the presenter you will find the logic that decides
- If there is any action that should be performed by the Interactor(s)
- If a navigation action should be performed
- If the view should be updated and what would this update be
- The presenter should be UIKit independent
- The presenter has no notion of models and entities
Interactor:
This class operates as a provider for the requests of the presenter. It is responsible to provide the presenter with data fetched from local or remote databases and contains business logic related to Entities. Interactors should define a use case, be easily testable and be as reusable as they can be. Ideally, an interactor should be reused by different presenters, even different targets and projects without the need to change any code.
Example: An Interactor responsible to perform a login action should be able to do that in an iOS, iPad and macOS application without any change.
Long story short: Inside the interactor, you will see:
- Ways to retrieve data (network calls, database calls)
- Business logic functions (entity validations, entity transformations)
- Requests to other modules of the app
Note: As we mentioned above, the interactor is responsible for entity manipulation. This does not mean that inside an interactor we are going to find functions that fetch data from the network or store data to a database. If you want to build your application in a clean way you should have different objects to perform this actions into separate layers. At the end you will need to inject these objects inside the interactor as dependencies, making the interactor completely testable.
Router:
This class is solely responsible for the navigation inside a project. It does not contain any kind of logic. Keeping the Router free of logic gives us the ability to have an easier tested navigation in unit test level.
Entity:
This class could be something like the Model in MVC or MVP but less functionality. It should contain the necessary properties and some functionality but in no way the same amount of functionality as it used to have in the aforementioned patterns.
We try to gather as much functionality as possible in the interactor level. Having multiple small and reusable interactors fetching, storing or manipulating data increases the speed and productivity in the project. On the other hand that doesn’t mean that the entity should be stripped down from functionality but it should contain functionality related only to the entity and nothing more.
Note:
Before I continue to explain how the main elements of are connected I would like to remind you that the true strength of VIPER is the separation of concerns.
Connecting VIPER
Trying to connect the VIPER elements is relatively easy, especially if you check the diagram posted above. The different elements of our architecture need to have a reference to one another. I order to avoid memory leaks please pay attention to the weak and strong connections between the elements.
Even though the Presenter seems to be the centre of the VIPER architecture, when I think of VIPER I always prefer to start from the View.
The View is only connected with the Presenter. It holds a strong reference to the Presenter. This is helpful for memory management as if the View is de-allocated there is no reason to have a presenter. Thus the presenter is going to be de-allocated as well.
The Presenter, as you can see from the diagram above is connected to the View, the Router and the Interactor.
In detail:
The presenter holds a weak reference to the view, a strong reference to the Router and a strong reference to the interactor. The benefit of having something like this would keep the elements of router and interactor alive only for the needs of the presenter. If the view is deallocated then the presenter is going to be deallocated and in sequence the interactor and the router.
The Router should not hold a reference to the presenter. It exists to aid the navigation requests of the presenter and nothing more.
The Interactor knows about the entity. In some cases, the interactor might initialize an entity or in some other cases the interactor will fetch data and transform them into the entity.
The Entity should be independent and agnostic of the interactor. It shouldn’t connect to any other VIPER element.
Conclusion
To sum up the VIPER pattern has the following:
Plus:
- Separation of concerns
- Increased testability
- Increased reusability
Minus:
- If the developers are not careful, the project my end up having spaghetti code
- The pattern is too verbose for small applications
- The on boarding time for new members of the team might be higher as they need to adapt their way of working to VIPER.
I hope you enjoyed the article. I will try to upload a hands on tutorial about VIPER soon. So stay tuned!!!