Model-View-ViewModel in Swift
Model-View-Controller, or MVC for short, is a widely used design pattern for architecting software applications. Cocoa applications are centered around MVC and many of Apple's frameworks are impregnated by the pattern.
A few months ago, I was working on the next major release of Samsara, a meditation application I've 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, however, lives a fat view controller. This view controller is responsible for managing the content of the table view and the data that is fed to the table view.
Table views are flexible and cleverly designed. 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 MVC pattern in action. The model layer hands the data source (mostly a view controller) the data the view layer (the table view) needs to display. But table views also illustrate how the MVC pattern can fall short. Before we take a closer look at the problem, I'd like to take a brief look at the MVC pattern. What is it? And what makes it so popular?
What Is It
The MVC pattern breaks an application up into three components or layers, model, view, 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. They only know how to present data to the user. They don't know or understand what they're presenting.
The view and model layer are glued together by one or more controllers. In iOS applications, for example, that glue is a view controller, an instance of the
UIViewController 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 MVC application. The view and model layer 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 clear separation of concerns. Each layer of the MVC pattern is responsible for a clearly defined aspect of the application. In most applications, there's no confusion about what belongs in the view layer and what belongs the model layer.
What goes into controllers 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 easy to reuse. If the MVC pattern is correctly implemented, the view layer and model layers should be composed of reusable components.
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 the MVC pattern. Why is that? What's wrong with the MVC 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 layer. No problem. Dump it in the controller. Problem solved. Right? Not really.
Data formatting is a common task. Imagine that 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's 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 overweight controllers, ready to burst and impossible to test. Isn't MVC the best thing ever?
How Can We Solve This
In recent years, another pattern has been gaining traction in the Cocoa community. It's commonly referred to as the Model-View- ViewModel pattern, MVVM for short. The origins of the MVVM pattern lead back to Microsoft and it continues to be used in modern Windows development.
The MVVM pattern introduces a fourth component, the view model. The view model is responsible for managing the model and funneling the model's data to the view via the controller. This is what that looks like.
Despite its name, the MVVM pattern includes four major components, model, view, view model, and controller.
The implementation of a view model is usually straightforward. All it does is translate data from the model to values the view(s) can display. The controller is no longer responsible for this ungrateful task.
Because view models have a close relationship with the models they consume, they're considered more model than view.
MVVM in Practice
I have to warn you, though. MVVM will change the way you write and think about software development. But that's a good thing. The following tutorials take a closer look at MVVM in practice.
In these tutorials, I use Samsara as an example to apply the MVVM pattern. The goal is to refactor the settings view of Samsara using MVVM. With the help of MVVM, I end up with a skinny view controller and several view models.
Along the way, you learn more about the MVVM pattern and how it applies to Cocoa development. You also learn how Swift can help us elegantly apply the MVVM pattern thanks to protocols, protocol extensions, and enumerations.