Understanding and implementing VIPER architecture for iOS applications -Part 1 : VIPER vs MVVM
This story is the first of a series of articles that will aid you in successfully understand and implement a VIPER architecture in your iOS applications using Swift.
In the following stories we’ll be using Swift 5 and UIKit to rapidly build an iOS app from the ground up with VIPER as architectural design pattern. To start our journey we will compare VIPER to MVVM (model-view, view-model), the most used architectural pattern for building iOS apps.
Layers of the VIPER architecture
V.I.P.E.R is an acronym for five architectural portions of the application that communicate with each other, the name stands for:
View: Part of the architecture responsible
Interactor: Abstracts the interaction with the entities and notifies the presenter for updating the view
Presenter: Transforms data so that it can be displayed in the correct format by the view
Entity: The data model behind the application
Router: Manages the coordination and the movement between different views
As you can guess, VIPER is about delegation of responsibilities, trying to follow the guidelines laid out by the SOLID principles more strictly. Each portion of the VIPER architecture is designed to accomplish just one responsibility, and communicates only with adjacent layers to prevent a disordered building of the application, at the cost of increasing underlying complexity.
Communication between layers
One note about the acronym is that it doesn’t contain information about which layers communicate with which other layer. For that reason is important to understand and establish beforehand the contract between the architecture units, to disallow them from being able to interact with every layer recklessly.
The diagram shows what is the structure of the layers communication. Arrows indicate what are the possible communications between components. One important thing to notice is that, being VIPER a layered architecture, there is no relationship linking between units that are not adjacent to each other.
Differences with MVVM: Behavior
While VIPER enforces a strict contract between components of the architecture by adding some in between layers of complexity, MVVM is an architectural pattern that is mainly about splitting the concerns of showing and handling the data. How MVVM is usually implemented is by letting the View store a reference to the ViewModel, with the latter being the component delegated to communicate with the underlying model of data by managing the binding.
In general, the principle of the MVVM architecture when dealing with UIKit can be described as such:
ViewControllerinforms the referenced
ViewModelthat it needs to fetch some data by calling a VM method
ViewModelstarts the process of data fetching using the
Modelfor encapsulating and representing the data
ViewModelupdates its property at the end of the successful fetch AND implements both the binding and the conversion of the data for the view to be displayed.
ViewControlleris informed and reacts to the change by displaying the correct updated data
On the contrary, a typical flow represented by a VIPER architecture using UIKit can be written as follows:
Interactorto load/reload the data it needs by using the
ViewControllerdoes NOT know who the specific
Interactoris and is in the dark about how the communication with it is executed(i.e: is not informed of the API contract).
Presenterinstead, holds a reference to the
Interactorand notifies it about the need of the view to receive updated data
Interactorreceives the message and triggers the new data fetch using the
Entityfor holding the data.
- On success, the
Interactoradvertises to the
Presenterthat data has been received and encapsulated correctly, and passes the data to it
Presenterthen receives the data and executes the needed conversion and transformation on it in order to give the
Viewthe correct format for the informations to be displayed correctly
The purpose of the
Router class instead, is to configure the whole module by linking the references between the layers at the beginning of the navigation, and handles the requests to change view by the Presenter.
MVVM vs VIPER
To make the differences more clear, given that some of them can be very subtle, the following table summarizes the responsibilities of the architecture units and what they accomplish
The first thing that can be easily noticed is that VIPER decouples the responsibilities that once where all given to the ViewModel in the MVVM by delegating them to other layers, so that each class accomplishes to just one responsibility at any given time.
Another core difference is that in the VIPER architecture each View has its own stack of IPER layers, while frequently in MVVM implementations the ViewModel can be shared by different Views by getting shared down the hierarchy of different views, especially when SwiftUI is used.
Common pitfalls of MVVM
Pitfall 1: Abusing the
ViewModel in SwiftUI
Regarding SwiftUI, there would be a lot to say of how MVVM is usually implemented. Given the different paradigm of development that SwiftUI allows, is very easy to make a design mess by letting different views bind and observe the same
Presenter. On of the reasons for the fundamental misunderstandings is that what is commonly written as View in SwiftUI, in reality is actually a Model that conforms to View via the
var body: some View computed property. Given the complexity and the delicacy, this topic will be skipped here (at least in this part), you can read more about it here.
Pitfall 2: Using
ViewModel as a network request manager
Another common pitfall is that in a lot of projects (both in UIKit and SwiftUI) the
ViewModel serves the only purpose of handling and managing the network request for the view itself, and it does so by directly calling
AFRequest leveraging Alamofire. This can easily lead to bad design when the size of the projects increases, because by definition a ViewModel is not a shared instance, and when dealing with complex REST APIs you’ll probably have to handle authentication, task concurrency, and task failure.
Is it wrong for the ViewModel to handle network requests?
ViewModel handle the logic of network requests can sometimes be fine, especially in smaller iOS application. But as the number of views/viewcontrollers grows you’ll find yourself either having different ViewModels that end up doing the same thing, duplicating code and forcing yourself to maintain the network calls in two different parts of the codebase, or the same
ViewModel that gets passed around views over and over again (see pitfall 3).
This could even be fine if view models didn’t have so many other responsibilities (see the table above). Instead of delegating ViewModels to lift this rock, having one or more shared API managers that serve the only purposes of managing and handling network requests and session is for sure a more accurate approach.
Pitfall 3: Sharing
ViewModel between many
How many times you were working on a View in SwiftUI triggered by a NavigationLink or Modal display and were trying to display rows of data only to realize you need part of the
ViewModel (calls/entities) that was being used in the previous View?
If you ever had this problem you have probably solved it by either passing an
@EnvironmentObject down the hierarchy letting the whole hierarchy now aware of that
ViewModel or you passed it trough a direct binding in the initialization parameters.
So what is the problem of that?
ViewModels are reference types. This means that if you pass the same instance in different parts of your code you’ll always have the risk of having side effects on your object and compromise its integrity, because you no longer have the protection given by the immutability.
Why VM is shared around often is self-evident, you’ll often have to make a lot of network calls and your following View/ViewController (maybe a DetailView) needs to fetch from remote the details of an entity given its ID (see pitfall 2).
In particular, if you choose to follow the shared
ViewModel approach, the VM you must hold properties for both the parent view and the child detail view, because they are intrinsically used to display the correct data on the screen. The side effect is that the parent view is made aware of what the detail view needs, even though it doesn’t need at all to know about it, breaking the separation of concerns and could theoretically trigger changes in the observed property.
Part 1 — Conclusion
The next part of the series will deal with what a VIPER architecture tries to do to solve common pitfalls and problems of the MVVM approach and how it structures the communication between layers by enforcing a contract using Swift Protocols. Given that VIPER has its own flaws and pitfalls too (that can easily lead to over-engineering) we’ll be also analyzing when using VIPER makes sense and how to avoid writing boilerplate code.