- What is VIPER
- What does it need for
- Dependency injection
- Using UINavigationController to navigate between modules
- TableViewDelegate and TableViewDataSource
- Transfer objects between modules
What is VIPER
VIPER model is separation the MVC into few parts. These parts are called View, Interactor, Presenter, Entity, Router.
View — is showing content on the screen, i.e it contains some UIViews. The view is passive and has no business logic here.
Interactor — all business logic of application should be here.
Presenter — it provides data from Interactor to View and vice versa.
Entity — class which contains only properties. For example, Human class that has properties like age, weight, height. Entity as like a View has no business logic.
Router — responsible for module routing, i.e navigation between application screens.
Interactor can’t contact with View and vice versa. It has the same situation with Router. Presenter works as a connector between all of them.
The parts of the module should be wrapped by protocols and connect with each other only by protocols, except Router. Also, we can’t pass Entities between parts, only Interactor can work with Entities.
What does it need for
1) It helps to add and change the code. Methods are more atomic because of using the principle of single responsibility. That’s why programmer doesn’t need to rewrite all code when he wants to add some new feature, he should only work with few methods. It greatly accelerates development.
2) It makes the code more testable, because of more atomic methods too — they are not black box for testing with a big variety of results.
Everything is clear and easy in theory, but there are some troubles in reality.
The first and main problem — dependencies between parts of VIPER-module (Presenter, Interactor, etc.) There are 2 ways to solve this problem:
1) Inject all dependencies by own hands.
2) Use some libraries for dependency injection (for example Typhoon).
The first variant is more complicated and wastes more time than the second. A programmer has to inject dependencies in the right way and do it without memory leak. Why it’s leaked? It happens because of double connections — Router has a strong reference to Presenter, on the other hand, Presenter strong reference to Router. The same thing is between View and Presenter. To solve this problem we use 1 weak reference to object (from Presenter to View, from Presenter to Router) instead of 2 strong. So there are no cycles now and the object will be destroyed when it becomes useless. The main advantage of this method — everything is clear and the programmer controls the lifecycle of objects.
Using libraries can make a programmer happier because he doesn’t have to think about dependency injection. But there is a new problem — libraries could be changed or disappeared. Taking into consideration the fact that the library is responsible for connectivity of application, missing of this library will be catastrophic. If it happened, the developer has to find a new library or inject all dependencies by himself. Anyway, it wastes a lot of time.
Another trouble is that big part of libraries and some standard features of Swift and Objective C aren’t compatible with VIPER (for example UITableVIew). It often happens because the principle of single responsibility is breaking by these things. Some of these errors we found in our project — before changing application structure to VIPER, we use some textField which has his own email validation. It’s called from an instance of this textField class. So there is logic inside View-layer. There are some ways to solve it:
- forget about single responsibility principle and leave well enough alone;
- send this textField to Interactor, but it’s not simple object => breaking the VIPER principles;
- remove a library from a project and write validation methods inside Interactor by our own hands;
There is not so big problem here, which is solved very fast (we choose last way and write everything without a library), but sometimes it can be very difficult to find a way to fix it and not break the VIPER principles. Anyway, it wastes a lot of time and requires a clever developer.
Mutual mobile recommends to merge all dependencies in one method and call it in appDelegate class when an application is starting. This approach has two serious disadvantages:
1) If an application has many strings of code, the method with dependencies will be extremely huge and hard to understand.
2) When this method is calling all this heap of depending on each other objects is wasting a lot of memory.
The first disadvantage fixes fast and easy. It can be solved by dividing this method into several parts. Every part responsible only for 1 VIPER-module. The situation with the second disadvantage is much more interesting. The best solution is “lazy” initialization. I’ll show it in a few steps:
Let’s create class Assembly for every VIPER-module, which has assembleModule method:
This method collects all dependencies of its module and return Router.
There are 2 modules: Start and Options. Start is the first screen, then with tapping button, we are going to Options.
Inject dependencies for Start-module with StartAssembly.assembleModule( ) method. After that we are making method injectOptionsDependencies inside StartRouter:
Next step is calling the injectOptionsDependencies( ) in presentOptions( ), which navigate us to Options screen.
So, now memory isn’t wasted for anything.
Using UINavigationController to navigate between modules
As a navigation system we use UINavigationController. Create method pushViewController in Router for it:
NavigationController has a strong reference to View, View to Presenter, Presenter to Interactor and Router. So we have that NavigationController hold all module. As a result, this solution allows us to destruct all module after ViewController will be removed from NavigationController.
TableViewDelegate and TableViewDataSource
The method, which we recommend to use, is not the only possible way. It was created for lack of anything better. Let’s show it with an example.
There is an array of Human objects, which is asynchronously received with the help of some API.
Human has properties:
- Name: String;
- Age: Int;
- Photo: UIImage;
For displaying information the UITableView is using. Name, age and photo of Human are loaded into a cell.
One of VIPER principles says that View is passive, but Presenter must return nothing to View. A view should have setters, which Presenter use for displaying information. The problem is that it works only for objects which are already created, but not for dynamically created. It means you can’t write a setter for every cell. Why is that? First of all, when an application is written, the quantity of cells is unknown. Secondly, writing setters for the same objects are foolish.
Let’s create a variable for each property of Human inside View, then add a setter for every new variable.
Next, create a method in Presenter, which gets data from Interactor and set it to “currentHuman” variables from View:
In this way, every cell is filled with the right data. It corrupts principle, which said that View shouldn’t have anything except references to UIViews and to Presenter. But it observes another rules and code is still testable.
P.S. This article was written by BytePace mobile developer. You can look at his works and at the other articles here: http://bytepace.com/