MVVM: A non-reactive introduction
Note: Sample project can be found here.
I love MVVM. Ever since adopting it many moons ago the ways in which I architect my apps have vastly improved. I personally credit it to the freedom MVVM allows. It’s a pattern that works hand-in-hand with concepts like “Separation of Concerns” and “Dependency Injection” as well as Functional Reactive Programming.
But it wasn’t always that way… It took me a while to absorb MVVM — I found the tutorials online daunting because they almost exclusively used frameworks like ReactiveCocoa or RxSwift to perform the “binding”. If you have no experience with those frameworks it makes something as easy as MVVM seem much more complex than it actually is.
I promise you… MVVM is a simple pattern.
What I will do here is introduce MVVM using nothing but built in Swift constructs like closures. FRP is nice and something everyone should try at some point but for now, let’s save it for another day…
What is MVVM?
MVVM stands for Model View ViewModel; these represent the three different components that will work together to make your UI easier to manage and test.
MVVM looks like this:
This diagram shows us how the components are related and the flow of the inputs/outputs. Let’s have a quick look at each of them and their general characteristics.
This represents the underlying data object in its raw form. In the case of iOS this will perhaps be your CoreData or Realm entity. The model is isolated in an MVVM setup. This means it contains no business logic and has no knowledge of the ViewModel or the View.
The ViewModel is the brains of the operation; It will have knowledge of the Model but no knowledge of the View. A ViewModel will take pieces of data from the model and turn them into values that are appropriate for your user interface. It will also provide some functions that can be called to perform certain tasks; these are generally triggered by user actions such as a button tap. Finally the ViewModel can raise events which can notify other parts of the application of a change that occurred. These can be used to trigger things like navigation to other screens.
A View is your user interface. It’s the layer that displays content and takes user input; in iOS this can be custom UIViews, table and collection view cells as well as UIViewControllers. It has knowledge of the ViewModel but nothing else. It will use the ViewModel’s values to populate the user interface and when a user performs an action it will forward it on to the appropriate ViewModel function to do the work.
A Practical Example
Here is our app! It lists all your friends, when you choose one you can see more details about them. (Full application code available at the end)
We are going to focus on the friends list and learn how to apply MVVM. This will allow us to work with a tableview and tableview cells.
First we start with the model layer which, as I mentioned earlier, is a basic representation of the data that drives your app. For our app we will use a Friend model object that matches the JSON we get from the server:
Simple right?! There is nothing fancy here…
This is the most important part of MVVM. You should have one ViewModel for each data driven View in the application.
For our friends table view we have two of those views, the UITableView and the UITableViewCells it displays. So we’ll create a ViewModel for the table view which provides the complete set of friends data, and a ViewModel for the cells, which provides an individual friend’s data.
We’ll start from the inner most element and work our way out. Our cell will expose three events:
The error event sends along info about anything that went wrong. Our update event will notify the View that changes have been made and it needs to update. Finally the select friend event which will inform our app that a friend was selected and we need to display their details.
You can see that these are just closures. We will use them to ‘emit’ events. This lets us keep our ViewModel simple and only responsible for things that are relevant. If something else needs to happen, i.e. showing an error dialog, it can just fire an event and some other object responsible for that can handle it. Nice right!?
The cell only needs to display our friend’s full name and avatar. Which means our output values will be:
Here you can see we are building the full name from the first and last name properties on our Friend model. The image will be retrieved and cached asynchronously so we make it optional and start without a value.
Next up are the functions the cell needs to perform. Firstly, we’ll need to handle cell selections. This is taken care of by using the CellRepresentable protocol which you can read about in this article. That just leaves fetching the friends image:
This will be called by the cell, which in turn will either fail or update our image property and notify the View it needs to update making it all nice and reactive! Sweet!
Moving up one level to the parent view controller which, much like our cell, exposes three events:
The error event does the same thing as our cell ViewModel, it sends along info about any errors. Likewise the update event is also the same, it notifies the View it needs to update. Finally, the select friend event is simply a ‘proxy’ for the cell event. Because the cells are encapsulated by the view controller they need a way for their selection event to make it ‘out’, we give the view controller a select friend event as a way of forwarding this information on the cell’s behalf.
The friends list needs to know information about what kind of cells to show, as well as the ViewModels for each cell (more on CellRepresentable here). It also needs to know what title to display and whether or not we are currently updating the data:
You can see here we are saying that our cells will be built with FriendCellModels and the data source starts empty. Our title will say “Refreshing…” when we are updating or “Your Friends” with the current friend count when we aren’t. Finally isUpdating keeps track of whether or not we are updating and fires the update event when the value changes.
Lastly we have the actions, all that is required of our ViewModel is to request data from the api:
Remember that each time we update our isUpdating property it will trigger our update event, this ensures our View stays in sync with the ViewModel.
Thats all there is to the ViewModels — hopefully you can see that there is a common ‘shape’ to the ViewModels. As actions come in, the ViewModel performs some work, then they notify when there are updates.
Views in MVVM are made up of your UI, in the context of iOS this includes view controllers and cells.
One of the core values of MVVM is to remove as much business logic as possible from the View. The result of this means that our Views become very lightweight and consist of little more than simple value assignments.
We will start again with the cell and work our way out, our cell code now becomes very boring :)
Here our setup function takes an instance of the ViewModel. The first line is crucial — because of the way cells are recycled and reused the cell ViewModel won’t always be paired with the same cell instance. To keep proper separation of View and ViewModel we have a small function on the ViewModel that can figure out if the cell being used is the correct one or not (see the code for FriendCellViewModel for the full implementation).
Once we know we have the correct cell we set the label’s text to the friend’s full name then the image if possible (otherwise a default is used).
The next part is important! We are telling the ViewModel that anytime there is an update it should call this function again. We don’t have to worry about a retain cycle here because the cell isn’t holding a reference to the ViewModel.
Finally we tell the ViewModel to go and fetch our friends image, this will eventually call the update event and, in turn, call the setup function again.
Much like our cell our view controller is now a lot slimmer than most! (I’ve omitted a very small amount of code for brevity)
As you can see here our viewDidLoad sets up listening to ViewModel events and requests new data.
Next we have our ViewModel functions. bindToViewModel makes sure the ViewModel events are sent to the functions that handle them. viewModelDidUpdate will fire each time there is an update and refresh the UI. Lastly viewModelDidError will just display an alert when an error occurs.
Our tableview has also become very simple using the CellRepresentable setup.
Notice that, with the exception of the friends default image, there is not a single if/else/switch of any kind. No. logic. at. all. A View should just show what it’s told and if it needs something it asks the ViewModel.
What’s the point?!
So why go to all the effort to use ViewModels? I’m glad you asked!
It’s not that UI is impossible to test, Apple have made some nice improvements recently. It’s just that it can be cumbersome and time consuming. Using MVVM you can simply write a unit test:
Now you don’t need to fire up the simulator or perform any scripted actions, you can even use your ViewModel’s actions to simulate user driven input.
Separation of Concerns: True View Separation
If you are really serious about a healthier code base, adhering to the “Separation of Concerns” or “Single Responsibility” principle is a big step towards that goal. I’m just gonna quote Microsoft (creators of the MVVM pattern) on this one:
Tightly coupled, change resistant, brittle code causes all sorts of long-term maintenance issues that ultimately result in poor customer satisfaction with the delivered software. A clean separation between application logic and the UI will make an application easier to test, maintain, and evolve. It improves code re-use opportunities and enables the developer-designer workflow.
With the introduction of the ViewModel you are immediately freeing your view controller (View layer) from the burden of ‘non-viewy’ things. You also open the doors for further decoupling using techniques like Dependency Injection .
Flexibility / Maintainability
Now that your Views are decoupled objects that only take simple data and perform simple actions, you are free to tinker! Swapping elements around or even complete redesigns of your interface become that much easier when you don’t have to tippy-toe around all the non-ui code that used to be there.
Hopefully I’ve been able to demonstrate a simple MVVM implementation and shown how MVVM can help lead you to a code base that is easier to test, maintain and also expand in the future.
Be sure to check out the sample project.
As always any feedback, good or bad, I’d love to hear about. If you found this helpful please recommend or retweet!