Mastering MVVM With Swift
Is the Model-View-Controller Pattern Dead
Model-View-Controller, or MVC for short, is a widely used design pattern for architecting software applications. Cocoa applications are centered around the Model-View-Controller pattern and many of Apple's frameworks make heavy use of the Model-View-Controller pattern.
A few years ago, I was working on the next major release of Samsara, a meditation application I have been developing for the past few years. The settings view is an important aspect of the application.
From the perspective of the user, the settings view is nothing more than a collection of controls, labels, and buttons. Under the hood lives a fat view controller responsible for managing the content of the table view and the data that is fed to the table view.
Table views are flexible and designed with reusability in mind. A table view asks its data source for the data it needs to present and it delegates user interaction to its delegate. That makes them incredibly reusable. Unfortunately, the more the table view gains in complexity, the more unwieldy the data source becomes.
Table views are a fine example of the Model-View-Controller pattern in action. The model layer hands the data source, usually a view controller, the data the view layer, the table view, needs to display. Table views also illustrate how the Model-View-Controller pattern can, and very often does, fall short. Before we take a closer look at the problem, I would like to take a brief look at the Model-View-Controller pattern. What is it, what makes it popular, and, more important, what are its drawbacks?
What Is It?
The MVC pattern breaks an application up into three components or layers:
- and Controller
The model layer is responsible for the business logic of the application. It manages the application state. 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
- and handling user interaction
A core principle of the MVC pattern is the view layer's ignorance with respect to the model layer. Views are dumb objects. A view should only know how to present data to the user. It doesn't know or understand what it is presenting. This makes views flexible and reusable.
The view layer and the model layer are glued together by one or more controllers. In an iOS or tvOS application, that glue is a view controller, an instance of the
UIViewController class or a subclass thereof. In a macOS application, that glue is a window controller, an instance of the
NSWindowController class or a subclass thereof.
A controller knows about the view layer as well as the model layer. This often results in tight coupling, making controllers the least reusable components of an application based on the Model-View-Controller pattern. The view and model layers don't know about the controller. The controller owns the views and the models it interacts with.
Separation of Concerns
The advantage of the MVC pattern is a separation of concerns. Each layer of the Model-View-Controller pattern is responsible for an aspect of the application. In most applications, there is no confusion about what belongs in the view layer and what belongs in the model layer.
What goes into the controller layer is often less clear. The result is that controllers are frequently used for everything that doesn't clearly belong in the view layer or the model layer.
While controllers are often not reusable, view and model objects are usually easy to reuse. If the Model-View-Controller pattern is correctly implemented, the view and model layers should be composed of reusable components.
If you have spent any amount of time reading books or tutorials about Swift and Cocoa development, then you have probably come across developers complaining about the Model-View-Controller pattern. Why is that? What is wrong with this popular pattern?
A clear separation of concerns is great. It makes your life as a developer easier. Projects are easier to architect and structure. But that is only part of the story. A lot of the code you write doesn't belong in the view or model layers. No problem. Dump it in the controller. Problem solved. Right? Not really.
Let's take a look at an example. Data formatting is a common task in software development. You are developing an application for creating and managing invoices. Each invoice has a due date. The format of the due date depends on the locale of the user.
The due date of an invoice is stored in the model layer. The view layer displays the formatted due date to the user. That is obvious. Which object is responsible for formatting the due date? The model or the view? Remember that the view shouldn't need to understand what it is presenting to the user. But why should the model be responsible for a task related to the user interface?
Can we delegate this task to the controller? Sure. Dump it in the controller. After thousands of lines of code, you end up with a bunch of overweight controllers, ready to burst and impossible to test.
How Can We Solve This?
Several years ago, another pattern gained traction in the Cocoa community. It is commonly referred to as the Model-View-ViewModel pattern, MVVM for short. The origins of the MVVM pattern lead back to Microsoft's .NET framework and it continues to be used in modern Windows development.
How does the Model-View-ViewModel pattern solve the problem we described earlier? The Model-View-ViewModel pattern introduces a fourth component, the view model. The view model is responsible for managing the model and passing the model's data to the view via the controller.
Despite its name, the MVVM pattern includes four components or layers:
- View Model
- and Controller
The implementation of a view model is often straightforward. In its simplest form, it translates the model's data to values the view layer can display. The controller is no longer responsible for this task. In the next episode, we take a closer look at the internals of the Model-View-ViewModel pattern.