The MVC pattern splits an application up into three layers:
- model layer
- view layer
- controller layer
It’s clear to most developers what goes into the model and view layers of an application. But what is the task of the controller? The controller glues the model and view layers together, but I’m sure you agree that it does quite a bit more than that in most applications.
And, all too often, this leads to overweight controllers and the Massive View Controller syndrome. Not only is it difficult to maintain such controllers, it makes them incredibly hard to test and adding features can be a nightmare.
There Must Be a Better Solution
A little over a year ago, I was working on a project that involved a complex configuration view. It included a table view with many sections and a bunch of table view cells.
Table views are great. They are simple to use and quite flexible. But I’m sure you agree that they are tedious to use if the data you’re presenting isn’t uniform. Take the Settings application as an example. How would you implement this with table views and view controllers? Can you imagine the code you’d write to implement the root view controller? And what would the implementation of the
tableView(_:cellForRowAt:) method look like?
I was facing a similar problem and I didn’t want to go the MVC route. Not this time. This was the first project I took MVVM seriously. There was a learning curve. I’m not going to lie. But the result was so beautiful and rewarding. I’m sure you know what I mean when I say the result was beautiful. We all know what beautiful code looks like. Right?
Not only was I able to keep the view controller lean and mean, I increased the testability and I could add and remove elements from the view controller with very little code and, more importantly, without being overwhelmed. Massive view controllers are often overwhelming, confusing, and … well … stressful. They can suck the lifeblood out of you. Right?
What Should/Shouldn’t a View Controller Do
Today, we take a closer look at what the controller is and isn’t in charge of. We also explore an alternative that doesn’t suffer from the same issues MVC suffers from. But let’s start by exploring each layer of the MVC pattern. This will give us a better idea of the problem MVC suffers from.
The model layer is in charge of the application’s business logic. It manages the state of the application. This also includes reading and writing data, persisting application state, and it may even include tasks related to data management, such as networking and data validation.
The view layer has two important tasks:
- presenting data to the user
- handling user interaction
A core principle of the Model-View-Controller pattern is the view layer’s ignorance with respect to the model layer. Views are dumb objects. They only know how to present data to the user. They don’t know or understand what they are presenting.
The view and model layers are glued together by one or several controllers. In iOS applications, that glue is a view controller, an instance of a
UIViewController subclass. In macOS applications, controllers are most often instances of a
A controller knows about the view layer as well as the model layer. This usually results in tight coupling, making controllers the least reusable component of an MVC application. The view and model layers don’t know about the controller. The controller owns the views and the models it interacts with.
Massive View Controller Syndrome
If you’ve spent any amount of time reading books or tutorials about iOS or macOS development, then you’ve probably come across people complaining about MVC. I hope it is clear by now what that is. Right?
One of the key benefits of the Model-View-Controller pattern is a clear separation of concerns or responsibilities. It makes your life as a developer easier. Projects are easier to architect and structure. But that’s only part of the story. A lot of the code you write doesn’t belong in the view or model layer. No problem. Dump it in the controller. Problem solved. Hmm. We now know how that ends.
Data formatting is a common task. Imagine you’re developing an invoicing application. Each invoice has a creation date. Depending on the locale of the user, the date of an invoice needs to be formatted differently.
The creation date of an invoice is stored in the model layer and the view displays the formatted date. That’s obvious. But who is responsible for formatting the date? The model? Maybe. The view? Remember that the view shouldn’t need to understand what it’s presenting to the user. But why should the model be responsible for a task that’s related to the user interface?
Wait a minute. What about our good old controller? Sure. Dump it in the controller. After thousands of lines of code, you end up with a bunch of massive view controllers, ready to burst and impossible to test. Isn’t the Model-View-Controller pattern the best thing ever?
Model-View-ViewModel to the Rescue
There are several alternative patterns that can help solve this problem. It introduces a fourth component or layer to the application architecture, the view model.
The view model sits in between the model and controller layer. It’s the view model that owns the model, not the controller. Let me show you what happens when the view controller needs to display a piece data in the view it manages.
The view controller asks its view model for data. The view model asks the model it manages for the raw value, a timestamp for example. It applies the necessary transformations to the raw value and returns a value the view controller can immediately display to the user in its view.
The view controller is no longer responsible for transforming the raw values of the model and it doesn’t even know about the model. That’s an important difference with the Model-View-Controller pattern.