VIPER Architecture and Solid Principles in iOS
In iOS development, as in any other mobile technology, most beginners start using as main architecture of their projects the very famous MVC(Model-View-Controller) or "Massive View Controller" as I like to call it. For first starters , I think this is the best architecture to learn due to its simplicity and low number of layers to take care about. For small projects, everything may work fine, since you don't have to care about several tests, there will not be huge and complex bugs and a few number of classes and structs may be enough.
MVC is the default architecture every Xcode project is made on. When you start something like a "Hello World" project on Xcode, all the MVC is ready for you: there is already a class representing the controller of your first screen(UIViewController), you have a storyboard with the interface of your initial view, and any other class that does not belong to the UIKit framework shall be seen as the model
The problem is, when you are working together with a bigger team in an app for a huge company, all care is little..Now you are working with a team of five developers, creating a new feature for a big application, like a payments app. Now you are managing other people's money and identities, so a lot of tests and complex code are coming into action. For this scenario, the traditional MVC pattern is not useful anymore, because it becomes very hard to fulfil the SOLID principles, which require every layer, class or struct to be independent of the other ones and do exactly what they mean.
The MVC pattern consider 3 layers
- View: All the user interface, views, containers, buttons, text fields and any other class that represents some component the user shall see.
- Model: The entities that build the logic of your application, every data model that takes part on the Entity-Relationship diagram of your system, like a person, an object, a user account, etc. Each class is only a data aggregation.
- Controller: What links the previous two layers. The controller manipulates all data within the models and shows it through the View layer. It also "listens" to user events with the UI and reflects it into the model layer.
But now you must be asking yourself: "If I just have three layers in my project, where should I place anything that does not correspond to a view neither an entity, like an API request, a use case, a database query or any other feature?"
The natural answer for the question is: The controller handles everything that is not view or model. It is such terrible, because now you will have a lot of methods, features and properties to be hold by a single class. That means a lot of testing, a lot of code to be understood by other developers and refactored. I did not even talk about the hard work to find out what is causing a specific bug.
Solution: Viper and Clean architectures
To better organise all the classes, structs and enums, the Clean Architectures first showed up to save the functionalities inside smaller layers which communicate with each other by using delegations and completions, each one with a single responsability, this way accomplishing the SOLID principles.
The Clean concept was first presented in Uncle Bob's book, which is a new way grouping the code classes into separated layers. It presumes a scheme where each layer does not have any knowledge of how the other ones work.
As you can see, each layer from an inner circle doesn't know anything about the others outside outside. The entities are used as actors by the use cases(which don't know about the entities), which are passed to the controller to show the data on the UI, databases, and other sources.
The most important benefit from Clean is the division above and the project must follow this abstraction scheme.
VIPER was born from Uncle Bob's Clean architecture and separates the content in five layers.
- View: Englobes all the interface classes like views, buttons, text fields and switches, and also the controllers(UIViewController, UINavigationController, etc..) since they are layers very close and have very similar concepts, since the view's lifecycle is tied to the controller
- Presenter: It is the central layer, that takes care of the user events triggered by the View, sends messages to the business layer and prepares the data from the use cases to be presented by the View. It also delegates a change of screen event to the Router layer.
- Interactor: The business rules layer. The Interactor, also known as Business, operates with the application entities to make the use cases happen. All the logic features like login, logout, fetch data, delete date, create object and others may occur in the Interactor layer.
- Model: Corresponds to all the entity classes and structs, being only an aggregation of data, such as in the MVC.
- Router: It is responsible for the navigation of the app. It has access to the navigation controllers and windows, using them to instantiate other controllers to push into them. Communicates only with the Presenter, and in some cases, with the view(when it wants to show a UI Alert)
- Manager: This layer does not exist in the original Viper and Clean architectures, but i think it is very important. It is responsible for customising request services to other data sources. Example: Preparing headers and attributes for an API request, building a query to an SQL database or any other kind of data . It is triggered by the Interactor
The relationship between those layers is shown above:
Pay attention to the relationship between each pair of layers: all of them communicate to each other through delegation(input and output protocols) or completion. It is essential to encapsulate the data inside and make the tests easier, since you can mock a class using its input protocol.
Scenarios
I spoke some time ago about the division of the application into the concept of scenarios. Different from other architectures, Clean and VIPER consider each screen or very similar screens due to a single functionality a scenario. Each View Controller(View layer) has its own Presenter, which communicates with one or more interactors(each one corresponding to a use case) and has its own Router, and a Manager if there is an API request or anything like that. The scenarios must communicate passing data one to the other through the Router. The Router is the interface between the scenarios which has the job of changing screens. Examples: Login Scenario passes the user's data to the home scenario.
Each scenario is kept in a different directory group of the Xcode project turning it easy to understand the pattern. The folder structure is made as below:
As you can see, the Xcode project has its template centered around two major folder called 'Common', which has all classes that are called all over the application and 'Modules', which has all the scenarios, each one corresponding to a single module in the app. The 'Modules' folder has a subfolder that contains all the VIPER structure of the Login screen and the Menu screen. Each one has its own interactors, its views, a presenter and a Router to communicate with other modules. It is important to notice that each VIPER layer inside a module must be another folder, since there might be multiple interactors or views and each layer may also have a builder class responsible for binding to other layers.
Model Mappers
There may also be more than a data structure representing a model. For instance, if you have a Person entity class in your application, this model is seen by the use cases as simple raw data, with only strings, numbers, etc. But if you consider how this must be shown in the view, the Presenter must convert the information sent by the Interactor to a convenient format that the UI can understand. The main model can see an image as a string identifier, but this has to be converted to an UIImage soon if you want the user to take a look on it.
The manager receives data right after it is fetched by the API, so it is natural it converts the JSON to a Swift class with all its fields. After that, it is converted to a suitable format as it is specified by the Entity-Relationship model and consumed by the use cases in the Interactor layer. After that, the model shall be transformed into something the View can show in the UI components, like somenthing near a ViewModel(take a look at MVVM).
The Mapper shown in the image above corresponds to a class that works as an adapter. The Mapper is responsible for converting a data model format to another kind of structure suitable for the next layer:
Take a look at how the model data is converted UI: All the fields within the Pokemon Details model are being converted into a single info String that will be a single UILabel content.
Illsutration Project: Pokemon Viper
Now it's time to show a very trivial example of how an app is built following the VIPER principles. It is a very basic application that consumes data from one of my favorite API's: PokeApi. The app has only two screens: A home with a list of all the pokemon from the first generation, since Bulbasaur until Mewtwo, and a second one that shows details of a single Pokemon after selecting it from the list.
Here we are going to show how the project was built, which are each of the VIPER layers and how the transtition between the two screens is made. We will cover one screen(module) per time and in each screen we will describe how each layer works in the VIPER scenario. Let's start by the Home(Pokemon List):
As you can see, the Pokemon List consists on a single UITableView showing each pokemon resume and UISegmentedControl to select the sort criteria: By id or alphabetically. By changing the segment, you reorder all the list.
1)View
You can check its view controller bellow:
Repair that the View Controller doesn't have any kind of logic deciding what must be shown. The View is only responsible for executing methods that change the layout of screen and delegate user interaction events to the next layer, in the case, the Presenter.
Check that every data to be shown to the user is requested to the presenter, which returns data from its View Model, like the count of pokemons, the content of a single pokemon and new list that is result of a reordering. You must also notice that every event with the UI, like "viewDidLoad", "buttonPressed" and "sortIndexChanged" is delegated to the Presenter input protocol. The Presenter may handle with the correct actions.
2)Presenter
As mentioned before, the Presenter is responsible for taking care of the events triggered by the View and feeding it with data to be shown. The Presenter has all the presentation logic(View Model) and works as a bridge isolating the UI from the business rules(Interactor) establishing a communication between them. It also triggers the Router if a change of screen may occur.
Notice that it has a reference to its output protocol, which in the case is the View, a reference to the Interactor input protocol and one to the Router. It contains a View Model object being the data that will be returned to the View when requested. Repair that there is an observer mechanism that requests the View to update every time the View Model is changed.
The Presenter answers to the Interactor when it sends data back as a result of the business operations. The Presenter is the Interactor Output and parses it to a format that the View shall understand(mapper). Notice that the Presenter requests actions to the Interactor, like "fetchPokemon"and "findSortOptions".
3)Interactor
The Interactor knows the Pokemon base model that works with the business rules and execute them. When requested, the Interactor returns the available sort options to the Presenter and fetch a pokemon based on the id the user has selected on the list.
The Interactor requests data to the manager, which communicates with the API and when it is returned, the Interactor send to the Presenter.
4)Manager
The Manager builds the request to the API and receives it as a model close to the JSON raw data. Then, it is sent back to the Interactor through completion.
5)Router
When it is time, the Presenter asks the Router for a change of screen. The Router has a reference to the main UINavigationController and pushes a new UIViewController to it. The Router also sends data from one module to the next one, as the pokemon id.
Notice that when it is instantiating the first screen, the Router only assigns a UINavigationController to the main window with the UIViewController as the root.
Details Screen
The second module works just like the first one. The details screen only takes the id of the selected pokemon and makes a second requests to fetch its details. It also has a manager that communicates with the API layer and the workflow is the same.
It is important to mention that when you press the "Next" and "Previous" buttons, it only requests for new pokemon data from the next or previous id, since it only changes the UIViewController data.
Conclusion
In this article, we learned an alternative architecture for iOS projects that may also be applied to other technologies. VIPER solves almost all the SOLID problems that happened in MVC and separates all kinds of features, since UI until API requests allowing them to behave as totally individual components to be tested solely.
We also showed a convenient way to establish a communication between the layers, through input and output delegation protocol or completion and a method to separate all the different contexts inside a mobile application. We divide each screen in a different scenario with its own VIPER layers simulating the behaviour of a single a organised app.
At the end, we concluded by illustrating a project that fetches data from an API and presents it to the user.
If you are interested in learning more about all this content, you can find the complete Pokemon project in my github: https://github.com/pnalvarez/PokemonViper
See ya! :)