Back to Home
MVVM Architecture in SwiftUI: From ObservableObject to @Observable - Part 1

MVVM Architecture in SwiftUI: From ObservableObject to @Observable - Part 1

Part 1: The MVVM and its integration with SwiftUI Development

Understanding MVVM Architecture Fundamentals

Core Concepts and Components

The Model-View-ViewModel (MVVM) is an architectural pattern, originally from Microsoft architects Ken Cooper and Ted Peters, designed to separate the graphical user interface from the business logic. It organizes an application into three key layers to improve how easy it is to maintain and test.

  • The Model is responsible for the application's data and business logic, such as domain objects and data access.
  • The View is the user interface that users see and interact with, responsible for displaying data and capturing user actions.
  • The ViewModel serves as a bridge between the Model and the View. It prepares data from the Model to be displayed in the View and handles user interactions and presentation logic.

MVVM Architecture Pattern Components and Data Flow

MVVM Architecture Pattern Components and Data Flow - wikipedia

The main goal of MVVM is to eliminate direct dependencies between the View and the Model. This separation keeps Views from being tied to specific Model implementations, which allows for greater flexibility and reusability. The ViewModel acts as a converter, transforming data from the Model into a format that the View can easily use. In this way, it functions as a mediator, organizing access to back-end logic based on what the View needs.

Architectural Benefits and Rationale

MVVM addresses several common problems in application development, particularly with user interfaces. One of the main reasons for its creation was to remove UI code from the view layer by using data binding. This approach, as detailed here, allows UI/UX designers to focus on the interface while developers work on the business logic. This separation of responsibilities helps teams work in parallel and can significantly boost productivity.

MVVM for iOS Development

For iOS development, we also care about the navigation between "screens" and their hierarchy, hence a separate Coordinator is needed. Those can be described with this cool diagram:

MVVM-C.png App Architecture - iOS Application Design Patterns in Swift

Advantages of MVVM over MVC

To fully appreciate MVVM, it's helpful to compare it to its predecessor, Model-View-Controller (MVC). In the MVC pattern, the Controller is responsible for handling user input and updating both the Model and the View.

While MVC separates concerns, it often leads to a tight coupling between the View and the Controller. The Controller can become a massive class that handles all the logic, making it difficult to test and maintain. This is especially true in UI frameworks where the View and Controller are often closely linked.

MVVM improves on MVC in several key ways:

  1. Enhanced Separation of Concerns: MVVM introduces the ViewModel, which acts as a more specialized intermediary than the Controller. The ViewModel is responsible for preparing and providing data for the View, but it has no direct reference to the View itself. This decoupling is a significant improvement, and the modular design makes it easier to test individual components in isolation, leading to more reliable and maintainable code.

  2. Data Binding: This is a core advantage of MVVM. The View is automatically updated when the ViewModel's data changes, thanks to data binding. This eliminates a large amount of boilerplate code that would otherwise be needed to manually update the View from the Controller. In SwiftUI, this is achieved through property wrappers like @StateObject and @ObservedObject (and more recently @State with @Observable objects). In UIKit or AppKit, it's usually one of the reactive frameworks such as Combine, RxSwift, ...

  3. Improved Testability: Because the ViewModel is not tied to the UI, it can be tested independently of any UI components without requiring complex UI component mocking. This makes unit testing the application's logic much simpler and more robust compared to testing a Controller that might be tightly coupled with the UI framework's APIs.

  4. Better for Collaboration and Reusability: The clear separation allows UI designers to work on the View while developers work on the ViewModel and Model, with less chance of conflicts. Additionally, MVVM promotes code reuse since ViewModels can be shared across multiple Views or even different platforms. This is particularly useful in cross-platform development, where business logic can remain consistent while the UI is adapted for each platform.

SwiftUI-Specific Advantages

SwiftUI's declarative programming style works very well with the principles of MVVM. The combination of SwiftUI's reactive data binding and MVVM's separation of concerns leads to better-organized and more maintainable apps. A key benefit, as noted in the SwiftUI Cookbook, is that the framework's built-in data binding eliminates much of the boilerplate code that was traditionally needed to sync ViewModels with Views. This lets developers focus on the application's logic rather than on manual state synchronization, which results in cleaner, more concise code.

The declarative nature of SwiftUI also benefits from MVVM's structured approach to state management. Views can declaratively bind to ViewModel properties, and they will automatically update when the underlying data changes, without needing manual refresh calls. This reactive system keeps the UI synchronized with the application's state while maintaining a clear separation between the presentation and business logic layers. The pattern also helps in organizing complex view hierarchies by allowing related views to share common view models through dependency injection or as environment objects.

Let's take some conceptual code examples to illustrate the difference.

Conceptual MVC-like approach in SwiftUI: In a pure MVC, the controller would handle the logic. In SwiftUI, views are more central, but one might be tempted to put logic inside the view, which acts a bit like a controller.

// A view doing too much, like a combined View-Controller
struct CounterView_PoorSeparation: View {
    @State private var count = 0

    var body: some View {
        VStack {
            // View logic
            if count > 10 {
                Text("Count is large!")
            } else {
                Text("Count: \(count)")
            }
            
            Button("Increment") {
                // Business logic inside the view
                self.increment()
            }
        }
    }

    // Business logic mixed in
    private func increment() {
        // Here we might have more complex logic, like checks or API calls
        self.count += 1
    }
}

The view above mixes presentation logic (the "if" statement) with business logic (the "increment" function). This becomes hard to test and reuse.

MVVM approach in SwiftUI: With MVVM, we separate this logic cleanly.

// The Model
struct Counter {
    var value: Int = 0
}

// The ViewModel
class CounterViewModel: ObservableObject {
    @Published private var counter = Counter()
    
    var displayText: String {
        if counter.value > 10 {
            return "Count is large!"
        } else {
            return "Count: \(counter.value)"
        }
    }

    func increment() {
        // All business logic is here, testable and separate.
        counter.value += 1
    }
}

// The View
struct CounterView_MVVM: View {
    @StateObject private var viewModel = CounterViewModel()

    var body: some View {
        VStack {
            Text(viewModel.displayText) // View only displays data from ViewModel
            Button("Increment") {
                viewModel.increment() // View only sends actions to ViewModel
            }
        }
    }
}

In the MVVM example, the "CounterView_MVVM" is simple and only responsible for layout. The "CounterViewModel" contains all the presentation and business logic, and it is easily testable without needing any UI. This separation makes MVVM a more robust choice for modern application development.

That's it for this part! In the next part, we will dive deeper into the MVVM Implementation with SwiftUI and its ObservableObject protocol and more. Stay tuned!