Our code design pattern of iOS MVVM Implementation
Numerous variables are involved in the choice of an architectural pattern. The choice is dictated by considerations on the complexity of the project, the ease and cost in terms of time linked to the development of the required features also with a view to maintenance and product evolution. Last but no least, it is also dictated by the experience gained in the field by the development team.
The experience gained with this pattern here in OverApp has allowed us to work on numerous projects of different complexity, convincing us of the quality of the MVVM solution.
MVVM: a brief recap
MVVM stands for Model, View, ViewModel, a specific architecture where the ViewModel stands between View and Model providing interfaces to connect to UI components. This connection is made by “binding” values, linking logical data to the UI.
Like other architectures, MVVM tries to satisfy at least the following principles
- Balanced distribution of responsibilities among entities with strict roles. We always have to keep in mind the Single Responsibility Principle (SRP) that states that every class or module in a program should have responsibility for just a single piece of that program’s functionality.
- Testability usually derives from the app complexity. A very feature-rich app can be tested effectively by setting up a series of automatic tests.
- Ease of use and a low maintenance cost.
How? Defining the following concepts:
The Model represents the data consumed by the application. A set of structs or classes that define information that would be used (and eventually rendered by a view). They could be eventually recovered through an API or a local DB or generated by the app itself.
The View only consists of visual elements. In the View, we initialize UI controls (such as labels, buttons, tables), setup the layout, perform animations, etc.
The ViewModel provides a set of interfaces, or properties if you prefer, each of which represents an UI component in the View. Interfaces and UI Controls are connected to each other with a technique called “binding”. Doing this, a change in a property of the model is automatically reflected on the UI.
The ViewModel also contains presentational logic. In general it obtains data from the Model and performs all the logic to prepare ready-to-display piece of data that should be presented by the View (for example converting dates into a readable form). Its data flow could be described in this way:
- The user interacts with the View (for example by pressing a button or an item in a list);
- The ViewModel receives the user interaction from the View;
- Then fetches data from the Model;
- And finally process the data to obtain a set of ready-to-display properties.
- The View updates itself after observing the change of the ViewModel.
The view updates itself
We said that the view updates itself after observing the change of the ViewModel. This is done using a technique called “binding”. How this binding is implemented?
In Swift, we can use different approaches:
- Delegate Pattern;
- KVO (Key-Value Observing) pattern;
- Third party libraries such as RxSwift, ReactiveCocoa or other libraries that offer FRP (Functional Reactive Programming) features.
Each of the proposed methods have pros and cons. In this article I will use the closure approach. Also the delegate pattern approach is a good choice.
In a hypothetical app that shows a simple picture gallery we could have a very simple Model like the following:
Here is the ViewModel:
Let’s examine it in detail. We have a property:
It contains data about a collection of pictures. It also has a so called property observer: didSet. Property observers observe and respond to changes in a property’s value. Property observers are called every time a property’s value is set, even if the new value is the same as the property’s current value.
So, once we set the value of the picture property a piece of code is executed:
It is a closure. If you go forward and read the rest of the code of the PictureListViewModel you can see that it is defined as:
Let’s take a look at the implementation of our View (our PictureListViewController) to understand how we can set this closure:
The implementation is really simple. We have:
- two outlets: one for an UITableView object and the other for an UIActivityIndicatorView;
- an adapter, we will discuss later about this object;
- our ViewModel of type PictureListViewModel
Now let’s discuss about a property called viewModel declared in this way:
We declared our ViewModel using the lazy modifier. This means that the property initial value is not calculated until the first time it is used. The first time the property is used it is instantiated.
Now focus your attention on the implementation of the InitViewModel function called by viewDidLoad, inside we can read:
It assigns a block of code to the property of the ViewModel we defined as:
At then end of the InitViewModel we can see the code:
Whatever is the implementation of the function fetchData in the ViewModel, we expect that it retrieves some sort of data in some way. At the end of the fetching operation we expect to obtain a collection of pictures and that the property “pictures” in our ViewModel is set with the information collected.
Now, put in order the information I provided until now:
- When the application starts, our PictureListViewController should appear on the screen.
- When the instance of the ViewController is created, a lazy property called viewModel of type ProductListViewModel is declared but it is not populated.
- The ViewController goes through the various steps of its own life cycle. When viewDidLoadis executed, some aspects of its UI are set via the function setupUI. Finally the viewModel is initialized through a specific function called initViewModel.
- During the execution of initViewModel we assign specific blocks of code to related properties of the ViewModel. It is the case of the properties called reloadTableViewClosureand updateLoadingStatusClosure.
- When we assign the first block of code to a property then viewModel is first used. This leads to the execution of the constructor of the ProductListViewModel class.
- When the constructor of the ProductListViewModel is executed it contextually create an object called apiService. We will describe soon what it is.
- The last line of code in the initViewModel function executes the fetchData function of the ViewModel.
- FetchData in some way recovers data. Forget how it retrieves data for now. When it successfully completes, the pictures property of the ViewModel is populated with the information collected.
- The pictures property of the ViewModel has a property observer (didSet). When the property value changes, it executes a closure called reloadTableViewClosure. The ViewController assigned to this property a block of code (see point 4 above).
- The block assigned to the property contains this simple piece of code: self?.tableView.reloadData()
- The code above forced a UITableView to refresh and display data. Somewhere we will have the implementation of the typical delegates for UITableView that will ask the ViewModel for the number of items to render, the type of cell to be drawn for an item etc.
Instead of implementing the required code to deal with UITableViews delegates inside the PictureListViewController class or through an extension, we choose to create a separate object that we call adapter. It behaves in a manner similar to the adapters for Android, it implements all the stuff required. By doing this, we write simpler ViewControllers, create specific objects to deal with tables or collection views. The adapter is implemented in this way:
This class implements an extension to conform to UITableViewDelegate,
UITableViewDataSource protocols. In the extension it implements all the delegates required for the UITableView. Look at the implementation of these delegates. See for example the function used to obtain the number of rows to display: it uses a specific property of type PictureListProtocol called delegate.
The adapter must be considered as a part of the view (the ViewController) and it uses the PictureListProtocol to communicate with the remaining part of the view. It is defined as follows:
When the adapter executes code to render the table on the screen and requires information such as the number of rows and of sections and finally the data that should be drawn in a cell, it executes the specific functions defined by the protocol that are implemented by the ViewController. The ViewController retrieves the requested data using it own property that represents the viewModel.
Now, let’s spend some words about the last piece of this puzzle, the apiService.
In the PictureListViewModel class we declared a property called:
It represents our APIManager, the class that is responsible for recovering information (for example remotely) that will be consumed by our ViewModel.
It is actually a dummy API Manager that reads a local text file containing a json. Its implementation is described below.
Remember that in the ViewModel class we implemented a function called fetchData? If we examine the implementation we see:
It uses the apiService property that represents our APIService to execute the function that gets popular pictures from somewhere. If the operation completes successfully, it assigns the obtained collection to the picture property of the ViewModel. We have already described what happens when we change the value of that property due to the existence of a property observer.
Due to the fact that lots of components are involved in our architecture we organize our projects in this way:
- We have a folder called Scenes that contains subfolders, one for every single view we need to implement.
- Inside each subfolder generally we have specific subfolders for Protocols, ViewModels, Adapters, Views, Cells
- Depending on the complexity of the project that may require a large number of views, we can have multiple specific storyboards that can be stored inside more subfolders.
You can read the complete article on Medium available here.