If you’ve been developing iOS applications for any length of time, you’ve probably heard of Model-View-Controller, or MVC. It’s your standard approach to building iOS apps. Lately, however, I’ve been growing tired of some of MVC’s shortcomings. In this article, I’m going to go over what MVC is, detail its weaknesses, and tell you about a new way to structure your apps: Model-View-ViewModel. Get out your buzzword bingo cards, because we’re about to have a paradigm shift.
Model-View-Controller is the definitive paradigm within which to structure your code. Apple even says so. Under MVC, all objects are classified as either a model, a view, or a controller. Models hold data, views present an interactive interface to the user, and view controllers mediate the interaction between the model and the view.
In our diagram, the view notifies the controller of any user interaction. The view controller then updates the model to reflect the change of state. That model then (typically through Key-Value-Observation) notifies any controllers of updates they need to perform on their views. This mediation makes up a lot of the application code written in iOS apps.
Model objects are typically very, very simple. Often times, they’re Core Data managed objects or, if you prefer to eschew Core Data, other popular model layers. According to Apple, models contain data and logic to manipulate that data. In practice, models are often very thin and, for better or worse, model logic gets shuffled into the controller.
Views (typically) are either UIKit components or programmer-defined collections of UIKit components. These are the pieces that go inside your .xib or Storyboard: the visual and interactable components of an app. Buttons. Labels. You get the idea. Views should never have direct references to models and should only have references to controllers through IBAction events. Business logic that doesn’t pertain to the view itself has no business being there.
That leaves us with controllers. Controllers are where the “glue code” of an app goes: the code that mediates all interactions between models and views. Controllers are responsible for managing the view hierarchy of the view they own. They respond to the view loading, appearing, disappearing, and so on. They also tend to get laden down with the model logic that we kept out of our model and the business logic we kept out of our views. That leads us to our first problem with MVC…
Massive View Controller
Because of the extraordinary amount of code that’s placed in view controllers, they tend to become rather bloated. It’s not unheard of in iOS to have view controllers that stretch to thousands and thousands of lines of code. These bulging pieces of your app weigh it down: massive view controllers are difficult to maintain (because of their sheer size), contain dozens of properties that make their state hard to manage, and conform to many protocols which mixes that protocol response code with controller logic.
Massive view controllers are difficult to test, either manually or with unit tests, because they have so many possible states. Breaking your code up into smaller, more bite-sized pieces is typically a very good thing. A recent story comes to mind.
Missing Network Logic
The definition of MVC – the one that Apple uses – states that all objects can be classified as either a model, a view, or a controller. All of ‘em. So where do you put network code? Where does the code to communicate with an API live?
You can try to be clever and put it in the model objects, but that can get tricky because network calls should be done asynchronously, so if a network request outlives the model that owns it, well, it gets complicated. You definitely should not put network code in the view, so that leaves… controllers. This is a bad idea, too, since it contributes to our Massive View Controller problem.
So where, then? MVC simply doesn’t have a place for code that doesn’t fit in within its three components.
Another big problem with MVC is that it discourages developers from writing unit tests. Since view controllers mix view manipulation logic with business logic, separating out those components for the sake of unit testing becomes a herculean task. A task that many ignore in favour of… just not testing anything.
Fuzzy Definition of “Manage”
I mentioned earlier that view controllers manage a view hierarchy; view controllers have a “view” property, and may access any subviews of that view through IBOutlets. This doesn’t scale well when you have many outlets, and at some point, you’re probably better off using child view controllers to help manage all your subviews.
Where is that point? When does it become beneficial to break things down? Does the business logic to validate user input belong in the controller, or the model?
There are multiple fuzzy lines here that no one can quite seem to agree upon. It seems like no matter where you draw those lines, the view and corresponding controller become so tightly coupled, anyway, that you might as well treat them as one component.
Hey! Now there’s an idea …
In an ideal world, MVC might work well. However, we live in the real world, and it does not. Now that we’ve detailed the ways that MVC breaks down with typical use, let’s take a look at an alternative: Model-View-ViewModel.
MVVM comes from Microsoft, but don’t hold that against it. MVVM is very similar to MVC. It formalizes the tightly coupled nature of the view and controller and introduces a new component.
Under MVVM, the view and view controller become formally connected; we treat them as one. Views still don’t have references to the model, but neither do controllers. Instead, they reference the view model.
The view model is an excellent place to put validation logic for user input, presentation logic for the view, kick-offs of network requests, and other miscellaneous code. The one thing that does not belong in the view model is any reference to the view itself. The logic in the view model should be just as applicable on iOS as it is on OS X. (In other words, don’t #import UIKit.h in your view models and you’ll be fine.)
Since presentation logic – like mapping a model value to a formatted string – belong in the view model, view controllers themselves become far, far less bloated. The best part is that when you’re starting off using MVVM, you can place only a little bit of logic in your view models, and migrate more of it over to them as you become more comfortable with the paradigm.
iOS apps written using MVVM are highly testable; since the view model contains all the presentation logic and doesn’t reference the view, it can be fully tested programmatically. The numerous hacks involved in testing Core Data models notwithstanding, apps written using MVVM can be fully unit tested.
The results of using MVVM, in my experience, is a slight increase in the total amount of code, but an overall decrease in code complexity. A worthwhile tradeoff.
If you look again at the MVVM diagram, you’ll notice that I’ve used the ambiguous verbs “notify” and “update”, but haven’t specified how to do that. You could use KVO, like with MVC, but that can quickly become unmanageable. In practice, using ReactiveCocoa is a great way to glue all the moving pieces together.
For more information on how to use MVVM in conjunction with ReactiveCocoa, read Colin Wheeler’s excellent write-up or check out an open source app I wrote. You can also read my book on ReactiveCocoa and MVVM.