The Myth of the MV pattern: Why SwiftUI developers just reinvented MVC

7 min read Original article ↗

Since the introduction of SwiftUI, the MV pattern has been discussed in online forums about SwiftUI architecture.

Its proponents sell it as a new pattern that aligns with how SwiftUI is intended to be used, while patterns like MVVM are allegedly at odds with the framework.

In this article, I will show how the MV pattern is just a rehash of MVC, and ideological adherence to this so-called new pattern only leads to violating fundamental design principles.

FREE GUIDE - SwiftUI App Architecture: Design Patterns and Best Practices

MVC, MVVM, and MV in SwiftUI, plus practical lessons on code encapsulation and data flow.

DOWNLOAD THE FREE GUIDE

Table of contents

  1. The SwiftUI data flow diagram presented by Apple is the diagram for the MVC pattern
  2. Aggregate models in the MV pattern are just MVC’s controllers with a fresh coat of paint 
  3. View models have never been against Apple’s frameworks, and they have existed for a long time
  4. MVVM’s view models fulfill roles that cannot be filled by any of the layers in the MV pattern

The SwiftUI data flow diagram presented by Apple is the diagram for the MVC pattern

The main evidence typically presented to support the MV pattern is an often-cited chart from Apple’s WWDC19 talk, Data Flow Through SwiftUI.

The widespread belief is that this diagram introduces a new paradigm for SwiftUI apps that is radically different than previous frameworks like MVC.

It is true that SwiftUI is a declarative framework based on a domain-specific language implemented using Swift’s result builders, which departs from object-oriented frameworks like UIKit and AppKit.

However, thinking that this means Apple is promoting a new design pattern betrays a lack of knowledge of design patterns and Apple’s history.

The truth is that the diagram Apple presented in 2019 is the same chart on page 10 of this document, which introduces MVC, created 40 years before the introduction of SwiftUI by Trygve Reenskaug.

This information is not hard to find. A little curiosity is enough to lead you to Wikipedia’s page for the MVC pattern, where you can find the same diagram, albeit upside-down.

Having developed apps for Apple platforms for more than 20 years, I also remember that Apple used to explicitly endorse the MVC pattern.

In fact, MVC is such a fundamental pattern in software development that it is no surprise that its successors simply rehash the same ideas.

Aggregate models in the MV pattern are just MVC’s controllers with a fresh coat of paint

The sleight of hand that removes the C from MVC and thus makes the MV pattern look new is the renaming of controllers into aggregate models.

This way, we can pretend they are part of the model layer, and that MV is totally not MVC.

But again, you don’t have to go further than the aforelinked Wikipedia page to read that:

The controller responds to user input and performs operations on data model objects. The controller receives the input, optionally validates it, and then passes the input to the model.

Finally, the aggregate models of MV are shared objects that can be accessed by multiple views and propagated through the SwiftUI environment, much like MVC’s controllers.

@Observable final class TransactionsController {
	func fetchTransactions(from start: Date, to end: Date) -> [Transaction] {
		// ...
	}
}

struct BudgetApp: App {
	@State private var transactionsController = TransactionsController()

	var body: some Scene {
		WindowGroup {
			ContentView()
		}
		.environment(transactionsController)
	}
}

struct TransactionsView: View {
	@Environment(TransactionsController.self) private var controller

	var transactions: [Transaction] {
		controller.fetchTransactions(from: .lastYear, to: .today)
	}

	var body: some View {
		// ...
	}
}

Dividing the controller layer into aggregate models and services is also not a novel idea. In a multitier architecture, it can be useful to have dedicated controllers that other controllers access solely for separation of concerns and basic encapsulation.

This proves a very simple fact: you can get rid of a letter in your design pattern acronym, but you can’t get rid of the role controllers fulfil. Call them aggregate models and services, if you like.

Basic software design principles and code reuse require shared objects accessed by multiple views, regardless of what you call these objects.

FREE GUIDE - SwiftUI App Architecture: Design Patterns and Best Practices

MVC, MVVM, and MV in SwiftUI, plus practical lessons on code encapsulation and data flow.

DOWNLOAD THE FREE GUIDE

View models have never been against Apple’s frameworks, and they have existed for a long time

Another claim from the supporters of the MV pattern is that you should “forget MVVM” in SwiftUI because “it fights against the framework”.

That is patently false. Even if you try to ideologically create a view model for each SwiftUI view, which you definitely shouldn’t, view models can be implemented with standard data flow primitives, even when using SwiftData.

The emergence of view models in iOS development can also be traced back to Apple’s guide.

For starters, you can combine the model and controller layers to obtain objects that are similar to what proponents of MV call services.

A model controller is a controller that concerns itself mostly with the model layer. It “owns” the model; its primary responsibilities are to manage the model and communicate with view objects.

More importantly, you can already see a concept similar to view models emerge.

A view controller is a controller that concerns itself mostly with the view layer. It “owns” the interface (the views); its primary responsibilities are to manage the interface and communicate with the model.

You could argue that view controllers match the aggregate models of MV. However, when Apple implemented the concept in UIKit, it enforced the rule that each view controller would control one screen in an iOS app.

Since aggregate models are shared objects, view controllers are closer to view models in MVVM than to aggregate models in MV.

MVVM’s view models fulfill roles that cannot be filled by any of the layers in the MV pattern

Finally, introducing view models in SwiftUI apps is necessary in specific circumstances, thereby invalidating the entire MV pattern and making it an anti-pattern.

  • SwiftUI views are Swift structures, i.e., value types. However, specific design patterns require the built-in identity of objects, e.g., delegation or the state pattern.
  • While SwiftUI views can share code through composition, Swift generics, macros, and protocol-oriented programming, there are cases where standard class inheritance is better implemented.
  • It is not possible to instantiate a view in a unit test to verify its behavior because of how SwiftUI works internally.
  • Cramming code inside a view is just bad separation of concerns and goes against the basic tenets of encapsulation.
@Observable
private final class ViewModel {
	var range = SummaryRange.year
	private let dataController: DataController

	init(dataController: DataController) {
		self.dataController = dataController
	}

	var totalExpenses: Decimal {
		expenses.map(\.amount).reduce(0, +)
	}

	var expenses: [Transaction] {
		let startDate = switch range {
			case .year: Date.lastYear
			case .last6: Date.sixMonthsAgo
			case .last3: Date.threeMonthsAgo
		}
		return dataController.fetchTransactions(from: startDate, to: .today)
			.filter { $0.type == .expense }
			.sorted(using: KeyPathComparator(\.category.name))
			.sorted(using: KeyPathComparator(\.date.month))
	}
}

Proponents of the MV pattern might argue that all of the above points can be achieved by aggregate models.

However, this code is view-specific and should not be moved into a shared object. That would violate a plethora of software design principles and create a god object.

Conclusions

The MV pattern is not new and merely rehashes ideas introduced by MVC 40 years before SwiftUI.

Apple has always endorsed, sometimes explicitly, the MVC pattern, which is the origin of any modern design patterns and is thus unavoidable.

However, the view models in MVVM fulfil roles that MVC layers cannot cover. Their ideological rejection violates fundamental software design principles, making MV an anti-pattern.

FREE GUIDE - SwiftUI App Architecture: Design Patterns and Best Practices

MVC, MVVM, and MV in SwiftUI, plus practical lessons on code encapsulation and data flow.

DOWNLOAD THE FREE GUIDE