MVC Swift vs MVVM on iOS: Key Differences With Examples
During the development of a modern iOS app, you need to select the proper architectural pattern for the project and end user. The choice should be based on many factors, such as expected app complexity, the experience of the team working on it, or the deadline for implementation.
Before you choose a pattern, you’ll need to understand how they work, as well as what problems they address and, in some cases, create. We can start by comparing two of the most common mobile architectural patterns in the iOS environment: the Model-View-Controller (MVC) pattern used by Apple together with third-party developers and the Model-View-View Model (MVVM) originating from Microsoft's ecosystem.
What is the Model View Controller Pattern?
MVC stands for Model-View-Controller – an architectural pattern separating an application into three main logical components, each of which is assigned to handle specific aspects of the application.
The MVC pattern divides the responsibilities of an iOS application into different sectors that serve their purpose. The MVC separates the business and operations side of the application from the presentation layer while tasking a middleman, known as the controller object, to facilitate interactions between the Model and View, including retrieving data from the database, manipulating it, and either sending it back to the database or using it for rendering.
MVC Pattern
First of all, let’s take a look at the three layers that organize code in the Model-View-Controller pattern:
-
Model – is responsible for storing the data but should not manipulate it for the View’s needs, as it is not aware in any way of the View. The model data is crucial as it is used to create views that accurately represent the output to the user, including various customer views with UI components.
-
View – has two responsibilities: it takes care of presenting the data to the user and receives the actions made by the user. It is not aware in any way of the Model – it is updated with processed data by its owner. Because of this characteristic, View should be devoid of any business logic.
-
Controller – owns and communicates with the View and Model layers. It’s responsible for updating the Model and the View with processed data from the Model. In the simplest setup, Controller is usually set as a delegate to the View or it sets up proper callbacks for the View to be able to update the Model and View based on user actions.
MVC Advantages
The MVC design pattern provides the following advantages:
- Simplicity and ease of use. Due to the small count of simply defined components, it is hard to find a pattern that would be more straightforward to introduce and maintain than MVC.
- Clear separation of concerns. It is obvious what should be part of either the View or Model.
- Reusability. Both View and Model are not tightly coupled with other components, so it is easy to reuse them.
- Vast Learning Resources. It is one of the most common patterns. It was the most frequently used in the early days of iOS development. It is widely understood by iOS developers, and many learning resources regarding its usage are easily accessible.
MVC Disadvantages
While having strong arguments for implementation, the MVC pattern could also cause the following issues during the development process:
-
Controller is kind of a catch-all object here. If a given task does not belong to the View or the Model, it usually ends up being implemented in the Controller, causing it to become a large and complex object. This problem is frequently referred to as Massive View Controller or Fat View Controller.
-
To ensure the quality of the code, at least the business logic should be covered with unit tests. In this pattern, this logic is placed in Controller – a complicated object tightly coupled with both View and Model. This complexity significantly hinders effective unit testing, making it very hard to write proper tests that can ensure code reliability and performance.
-
Direct result of those two drawbacks is reduced testability of this pattern. As the Controllers grow in size and complexity, they cause difficulty in covering the app with sufficient unit tests, along with problems in both maintaining and developing the application.
What is the MVVM Pattern?
MVVM, short for Model-View-ViewModel (MVVM), is an architecture pattern that enhances the separation of the graphical user interface from the business logic or back-end logic. Similarly to MVC, this pattern divides the application into the model and view components. However, unlike MVC where the controller mediates between the view and model, in MVVM, this role is fulfilled by the ViewModel.
This leads to a cleaner separation of concerns, with the ViewModel acting as the mediator and housing the business logic code that is separated from the view. The MVVM design pattern offers several advantages over other patterns, such as MVC and MVM, by providing a more efficient way to build interactive applications.
MVVM Pattern
To address the problems mentioned above, MVVM introduces one additional layer to the MVC setup. It is called View Model, and it replaces C with VM in the MVVM (even though Controllers are still present in this pattern). Please note that the Model and the View have the same responsibilities as in MVC.
-
Model – is responsible for storing the data but should not manipulate it for the View’s needs as it is not in any way aware of the View.
-
View – has only two responsibilities: takes care of presenting the data to the user and receives the actions made by the user. It is not aware in any way of the Model – it is updated with processed data by its owner. Because of this characteristic, View should be devoid of any business logic.
-
View Model – is a new layer between the Model and the Controller. It owns the Model and takes care of manipulating its data in a way that makes it ready to be displayed by a simplified View. Additionally, it enables data binding, which facilitates two-way communication between the view and the model, automating the propagation of modifications and ensuring a clean separation of concerns.
-
Controller – retains the responsibilities of setting up and coordinating the components it owns. Controller role is simplified in comparison to MVC because the business logic related to manipulating the Model’s data is moved to the View Model layer. The second difference is that it owns View Model instead of owning Model directly.
MVVM Advantages
Considering the introduction of the View Model layer, here are the advantages of the MVVM pattern.
- Clear separation of concerns. It is obvious what should be part of View or Model.
- Reusability. Neither View nor Model is tightly coupled with other components, which makes it easy to reuse them.
- Popularity. In the iOS world, MVVM is a newer pattern than MVC. Even so, it has become frequently used in recent years and many learning resources are within easy reach.
- Improved testability. In contrast to MVC, business logic is moved to the View Model, which makes it easy to test by mocking Model's data and checking View Model's properties and methods.
- Improved scalability and reduced complexity. MVVM increases scalability, which means that – with the growing size of the app as well as the count of the features and user flows – the complexity of its internal structure stays at a reasonable level longer than in the MVC pattern. This is especially true when taking into account the flexibility in choosing the way of communication with the View Model, depending on the case we can use the Delegate pattern, bindings, or callbacks. This scalability could be increased even further by introducing Coordinators to extract the app's navigation logic from Controllers. We would then call this new pattern MVVM-C.
Example of implementation in MVC and MVVM
Now, let’s consider a simple component in a to-do app that displays one task on some screen with a list of tasks to see the difference between MVC and MVVM. In both MVC and MVVM architectural patterns, the user interface plays a crucial role in transforming model objects into graphical components like HTML, CSS, and jQuery, making data accessible and interactive for the user.
Common parts in MVC and MVVM implementations
Here are: Task
(Model), TaskView
, and TaskViewDelegate
– those will be exactly the same in both implementations. In the View, we are skipping the implementation of the init
and setting up the tap action handling. Note that this step is not related to or affected by the architectural differences of the MVC or MVVM patterns.
struct Task {
var text: String
var isChecked: Bool
}
protocol TaskViewDelegate {
func didTap()
}
final class TaskView: UIView {
weak var delegate: TaskViewDelegate?
private let imageView: UIImageView
private let label: UILabel
// Skipping init and setting up of the tap action that triggers didTap delegate method
func update(with image: UIImage?, and attributedText: NSAttributedString) {
imageView.image = image
label.attributedText = attributedText
}
Example of Controller in MVC implementation
Here is the implementation of MVC's controller. In this example, the controller is responsible for owning the Model and transforming data into properties consumable by the TaskView
. This time, we are skipping the setting up of the view, as well as triggering the initial update; this part will be the same in both architectural approaches.
final class TaskViewController: UIViewController {
private let taskView: TaskView
private let attributes = [NSAttributedString.Key: Any]() // Empty, for demo purposes only
private var task: Task
// Skipping setting up the view and triggering initial update
func didTap() {
task.isChecked.toggle()
updateView()
}
func updateView() {
let image = UIImage(named: task.isChecked ? "checkedTaskIcon" : "taskIcon")
let attributedText = NSAttributedString(string: task.text, attributes: attributes)
taskView.update(with: image, and: attributedText)
}
}
Example of View Model in MVVM implementation
Now, let's see how we could implement a View Model that would own the Model, toggle its isChecked
value, and expose properties to the View that would be computed from Model's data.
struct TaskViewModel {
var image: UIImage? {
UIImage(named: task.isChecked ? "checkedTaskIcon" : "taskIcon")
}
var attributedText: NSAttributedString {
NSAttributedString(string: task.text, attributes: attributes)
}
private let attributes = [NSAttributedString.Key: Any]() // Empty, for demo purposes only
private var task: Task
init(task: Task) {
self.task = task
}
func toggleTask() {
task.isChecked.toggle()
}
}
Note that even though moving the business logic to the View Model increases the code-line count, the result appears more readable since TaskViewModel
has very obvious and limited responsibilities. In addition to that, writing unit tests for it is simple. For example, this is how a method testing the image property could look:
func testImage() {
// given
let task = Task(text: "Fixture Text", isChecked: true)
let sut = TaskViewModel(task: task)
// then
XCTAssertEqual(sut.image, UIImage(named: "checkedTaskIcon"))
}
Finally, let’s see how introducing the View Model will change the Controller in an MVVM implementation.
final class TaskViewController: UIViewController, TaskViewDelegate {
private var viewModel: TaskViewModel
// Skipping setting up the view and triggering initial update
func didTap() {
viewModel.toggle()
updateView()
}
func updateView() {
taskView.update(with: viewModel.image, and: viewModel.attributedText)
}
}
Conclusion from MVC and MVVM implementations
From the code above, we can see that adding View Model layer streamlined the controller visibly. The removed code is now encapsulated inside a well-defined struct. Take special attention to how much easier it is to test the MVVM approach because we can directly access the results of manipulating mocked data provided to the View Model. In MVC’s case, we would need to either:
- Test the
TaskView
object to check the logic placed in the Controller. Since we would be checking a system of two objects, this would actually be an integration test instead of a unit test. A drawback here would be that to test all possible configurations of view (n) and controller (m) we would need to write n*m instead of n+m tests. It is easy to see that, in this scenario, the cost of maintaining tests grows quickly alongside complexity. - Change the implementation of the Controller by putting the data manipulation code into two non-private properties. This would mean sacrificing the readability of the class for the sake of its testability. While this practice may be acceptable in an easy case, with growing complexity, it would be an increasingly difficult problem to cope with.
Summary
While selecting the architecture pattern is one of the most important responsibilities of a developer working on a mobile app, this decision has to be made considering the project's specific needs.
It is impossible to name a universally applicable pattern for software development, as all patterns have benefits and disadvantages that will fit some use cases better than others. If you are working on a simple project that you don’t plan to make more complicated in the future, MVC is an easy-to-implement pattern suited for your use case.
On the other hand, if you want your app to be a little easier to expand and maintain when it is getting more complicated, we have provided an example where MVVM streamlines the testing process and divides the responsibilities. MVVM could be a good choice for your app if you would like to avoid overgrown controller classes or facilitate the process of writing unit tests.