For my last post over my ZombieInjection experiment, I wanted to cover the overall MVVM architecture that I used in developing this example app. As I was doing some research for this post, I discovered that the way I learned MVVM was very different from what appears to be how many in the iOS community learned about MVVM.
I first learned of this architecture pattern while I was doing Xamarin apps in C#. This meant that there were a lot of articles and very mature examples on implementing MVVM in the .NET world that I could draw on to learn this powerful architecture pattern. As I compare what I learned in .NET to how the iOS community is typically implementing this, I am seeing some missing pieces on the iOS side. Namely, if all you are doing is taking a typical MVC application and refactoring it to fit this pattern, MVVM is probably not very compelling for you. In fact, some respected iOS developers have come to the conclusion that MVVM is Not Very Good.
New on the blog, “MVVM is Not Very Good” https://t.co/QF818iNmYl
— soroush (@khanlou) December 17, 2015
While Soroush’s post has some valid points, I believe it is missing a lot of context and incorrectly comes to the conclusion that this architecture pattern should just be ignored. I will attempt to fill in that missing context and Make MVVM Great Again!
Before we get into some of the missing context, I wanted to give a brief overview of MVVM. Disclaimer: This is MVVM as I know it. It seems like there are many different flavors and preferences but this is what I’m familiar with so I would love to hear other techniques or ideas to further simplify or enhance my own understanding! The graphic below is the best representation I’ve found for how MVVM looks in iOS:
Next, let’s define the different parts that make up the MVVM pattern as it sounds like there has been some debate as to what roles these components should play.
The model holds the actual data and/or information that we will be dealing with in our application (typically called the data layer). As its main goal is just to hold the data, it should not be holding *ANY* behaviors or logic that manipulates the data/information itself. It is *NOT* responsible for the beautification of the text on the screen or fetching new items from a remote server. In a perfect world, it is a very clean and simple structure. For example, the Zombie model structure in my example project is solely responsible for holding the id, name, image, and image URL — that’s *ALL* it does. (Other than holding a RealmPersistable that allows me to better isolate the rest of my app from Realm specific logic 😉)
Here’s what it looks like:
Side note: As somewhat of an MVVM purist I don’t particularly like having UI elements such as UIImage in my models but for simple things like this it’s much easier to just deal with it that way.
The view, in conjunction with the view controller, make up the “View” component in the MVVM pattern in iOS. The view is the thing that the user is actually interacting with in your application; it is also the place where you will be presenting data.
The most complicated view in my example project is the ZombieListViewController. It is responsible for surfacing a list of zombies in a UITableView, handling the data-binding between the view and the view model, and handling the navigation logic to the detail view for one of these zombies if you click on it. At some point, I’d like to find a way to push the navigation logic into the view model for easier testability but this will have to do for now.
My ZombieListViewController looks like this:
The view model is what controls what is being displayed in the view. It retrieves the information from your data-layer (your models), formats it if necessary (I’d love to figure out a way to implement ValueConverters in Swift), and passes it to your view. In my example, the view model is simply retrieving the data to be shown from a ZombieService and exposing the ImageDownloadService so that the view can show the correct image. After it has the data we need, it uses Data Binding (via the Bond framework) to actually update the view.
One of the main points of contention I’ve seen is that some developers have been advocating doing your network calls/fetching data directly in the ViewModel itself… Don’t do that.
My ZombieListViewModel is fairly basic; It handles the update of the list of zombies via the ZombieService and exposes the ImageDownloadService so that the view can download the correct image via it’s binding.
Now that we’ve gone over the high level detail in how I implemented MVVM in my sample project, let’s talk about what I’ve seen missing in many of the other MVVM examples I’ve seen during my research that appear to throw folks like Soroush off.
So, What Are They Missing?
Data binding is one of the staples of using the MVVM pattern in .NET. Unlike in .NET, there is no nice, built in mechanism for data binding in iOS. You either need to do it manually using Key-Value Observing (KVO) or use a framework like Bond. So far, I have loved the Bond framework because it provides a simple and concise mechanism to set up your data binding for multiple objects. For example, in my main list view controller I perform data binding for the text property and image property of all cells of the main table like so:
With that relationship set up, any changes made to the data get automatically displayed on the UI without any more additional work! This eliminates so much boilerplate code and makes things in general much more maintainable.
Dependency Injection (in combination with the MVVM architecture pattern) is especially powerful. When combined properly, you end up with a highly decoupled and highly testable architecture. For instance, this setup allows me to easily mock any of the injected dependencies in my project for my unit tests so that I can easily abstract out complex functionality that is not relevant to the specific unit test. Loosely coupled code doesn’t just enhance your testability — It also increases the reusability and extendability of your code.
My project is making use of the fantastic DIP framework. When using this framework, it is recommended that you set up a composition root (also known as the single place where you define *all* of your dependencies for your project):
As you can see above, by default it is set to use Realm as my data repository for my zombie objects; however, by commenting out 1 line of code (and uncommenting the other), I can easily switch to using an in memory data service based on arrays instead of Realm if I just need to debug something and don’t want to mess with Realm. The wiring of the actual AppDelegate looks like this:
The AppDelegate is in charge of resolving the specific dependencies I need and then injecting them into my application via the top level controller. In this way, I’m able to use protocol oriented programming to inject different dependencies just by changing my composition root as these protocols are propagated throughout the rest of the app. Speaking of protocol oriented programming…
Protocol Oriented Programming
If you’re not doing it — you should be.
I won’t go through and fully explain protocol oriented programming in this post, but I highly recommend you go watch the 2015 WWDC video introducing the concept. There are now a ton of resources dedicated to the topic and it will be well worth your time doing some research on it. That said, protocol oriented programming and the MVVM architecture go very well together. For example, take a look at my ZombieListViewModel again:
As you can see, there is a ZombieServiceProtocol and an ImageDownloadServiceProtocol. This allows me to inject whatever objects I want that conform to those protocols inside my ZombieListViewModel. While not immediately obvious as to why this is advantageous, let’s take a look at my unit tests in ZombieListViewModelTests:
As you can see above, this allows me to replace the objects normally expected by the view model with my mock objects. I can then create unit tests that actually isolate the code I wish to test instead of worrying about something like Realm in the unit tests, as it has been mocked out using arrays in my ZombieRepositoryMock. This is BIGLY important to a well architected app that needs to be highly testable.
This is also what allows the dependency injection previously discussed to work. The ZombieServiceProtocol is used everywhere throughout the app. The actual concrete class used for the ZombieServiceProtocol is defined in the composition root as discussed prior. The composition root registers the ZombieService I want and the AppDelegate injects it into the app to be used. Pretty neat, huh?
The concept of repositories was one that was pretty common to .NET land while I was there. I have not seen it much on the iOS side of the fence; however, I am HUGELY a fan of this design pattern. Using repositories allows you to separate your data layer cleanly from the rest of your application. Let’s take a look at my ZombieRepository as an example:
The big thing here is that this repository takes in a ZombieDataServiceProtocol via constructor injection. This is the magic sauce that allows me to easily swap in and out database types. For instance, I can use Realm or an in memory database simply by sending in different implementations of ZombieDataServiceProtocol. While in practice you are not likely switching database technologies frequently, you likely want to switch to a more manageable database type for your unit testing. Take a look at my ZombieRepositoryMock:
With that implemented, I can now use this well controlled, in memory array of objects to do all of my unit tests instead of having to mess with Realm at that level.
A Service Layer typically makes use of a repository and captures any of the business logic around that a particular item.
In my example project, the service that best demonstrates this concept is the ZombieService:
In this service, I have encapsulated all of the things you can currently do with Zombie objects in my application. You can fetch them from a service (Mocked out presently), you can update them, and you can get all of them that currently exist in your local repository. In this arrangement, any time you want to interact with Zombie objects — you have to go through the ZombieService.
You’ll notice this is the only place where you get access to the ZombieRepository. This nicely encapsulates the functionality and makes it highly decoupled and testable.
While some of the criticisms of MVVM on iOS are warranted, I believe that this architecture pattern should not be overlooked if implemented properly. When compared to other patterns like VIPER or clean architecture, I contend that you will get the same level of scalability, decoupling, and testability in MVVM without a lot of the confusion and overhead that comes with those other architectures. I hope with my ZombieInjection project I’ve been able to convince you to take another look at this architecture and hopefully I was able to Make MVVM Great Again!