Hello, world! 👋 It’s been a while since I’ve shared something with you all, and today feels like the perfect day to dive into an old, yet very relevant, topic. Let’s talk about a classic architectural debate in iOS development and how its lessons can help us avoid pitfalls with SwiftUI.
The Problem: Massive View Controllers (MVC’s Evil Twin)
Before 2019, many of us were deep in the trenches of the infamous Architecture Wars, battling over patterns like MVC, MVVM, and VIPER. At the heart of it all was the struggle to properly separate concerns in our codebase.

We weren’t mad at MVC itself; we were frustrated by how easily it evolved into Massive View Controllers.
Let’s break it down. The humble UIViewController often became an all-you-can-eat buffet of logic, responsible for tasks like:
Navigation logic
UI updates
Presentation logic
Business logic
Network requests
Caching mechanisms
Before we knew it, our view controllers ballooned to over 1,000 lines of code. 🥲
Was It Really MVC’s Fault?
Nope! As Dave delves into brilliantly in this article, the issue wasn’t the MVC pattern itself. It was our interpretation of where things should live.
Apple always recommended breaking things down:
Models: Split them into smaller reusable units.
View Controllers: Divide and conquer! Use child view controllers.
Views: Decompose complex layouts into smaller, reusable pieces.
With SwiftUI, Apple continues to encourage breaking things down further, promoting better modularity.
When we follow that, we avoid falling into the same rabbit-hole over and over again

But today, I want to focus on tackling logic, or the "M" in any MVx pattern. Enter: Command Pattern.
The Command Pattern: A Fresh Perspective
At its core, the Command Pattern is about encapsulating logic into small, focused units. Think of it as a class (or struct) with a single responsibility: an execute() method.
Here’s an example:
struct FetchPaymentMethods: AsyncUsecase {
func execute() async -> Result<[PaymentMethod], BusinessError> {
}
}
This does a few things immediately:
Keeps the view controller or SwiftUI view clean.
Follows the Single Responsibility Principle (SRP). 🚀
Improves readability and maintainability.
If this logic grows too complex, you can further break it down into smaller commands:
struct FetchPaymentMethods: AsyncUsecase {
func execute() async -> Result<[PaymentMethod], BusinessError> {
await [
FetchWallet(),
PreselectFavorite(),
DisableUnsupportedMethods(),
AddApplePayIfSupported()
].asyncForEach { await $0.execute() }
}
}
Why Use the Command Pattern?
Modular Expansion: Need to add a new step? No problem—just add a new command. Old implementations remain untouched.
🥳
Achievement Unlocked: Open-Closed Principle (OCP). 🚀
Composability: Combine multiple commands to create complex workflows. For instance, placing an order might look like this:
struct PlaceOrder: AsyncUsecase {
func execute() async -> Result<Void, BusinessError> {
await [
ApplyPromoCode(),
UpdatePaymentMethod(),
ValidateCheckout()
].asyncForEach { await $0.execute() }
}
}
Testability: Each command is a small, isolated unit—perfect for writing focused unit tests.
Reusability: Commands are decoupled from the UI, making them reusable across different platforms, whether you’re building for iOS, macOS, or even CLI apps. 😄
A Word on SwiftUI
While this pattern shines in UIKit, it’s equally valuable in SwiftUI. With SwiftUI’s declarative nature, logic can creep into views if you’re not careful. Using the Command Pattern ensures your business logic remains in its rightful place—clean, testable, and reusable.
Example Time

So, now that we’re almost done, let’s give some examples about how can we make this possible
UIKit: Where the Model is the one in Command
In MVC, the Model is responsible for managing the app’s data. By incorporating the Command Pattern, we can offload specific business logic into dedicated commands, keeping the Model lean and maintainable.
struct FetchPaymentMethodsUsecase: AsyncUsecase {
func execute() async -> Result<[PaymentMethod], BusinessError> {
}
}
final class PaymentModel {
private var paymentMethods: [PaymentMethod] = []
private var error: BusinessError?
var onPaymentMethodsUpdate: (([PaymentMethod]) @MainActor @Sendable -> Void)
var onError: ((BusinessError) @MainActor @Sendable -> Void)
init(…) { … }
func fetchPaymentMethods() async {
let result = await FetchPaymentMethodsUsecase().execute()
}
}
class PaymentViewController: UIViewController {
private let paymentModel: PaymentModel
init(…) {
paymentModel = init(onPaymentMethodsUpdate: {…}, onError: {…})
super.init(…)
}
override func viewDidLoad() {
super.viewDidLoad()
Task { await paymentModel.fetchPaymentMethods() }
}
private func displayPaymentMethods() {
}
}
SwiftUI: Where the Model is still the one in Command
In MVVM, the ViewModel acts as the mediator between the view and model. The Command Pattern fits naturally here, allowing the ViewModel to delegate logic cleanly.
struct FetchPaymentMethodsUsecase: AsyncUsecase {
func execute() async -> Result<[PaymentMethod], BusinessError> {
}
}
final class PaymentModel {
private var paymentMethods: [PaymentMethod] = []
private var error: BusinessError?
var onPaymentMethodsUpdate: (([PaymentMethod]) @MainActor @Sendable -> Void)
var onError: ((BusinessError) @MainActor @Sendable -> Void)
init(…) { … }
func fetchPaymentMethods() async {
let result = await FetchPaymentMethodsCommand().execute()
}
}
class PaymentViewModel: ObservableObject {
@Published var paymentMethods: [PaymentMethodUI] = []
@Published var error: PresentableError?
private let model: PaymentModel
init(…) { … }
func fetchPaymentMethods() async {
let result = await FetchPaymentMethodsUsecase().execute()
}
}
struct PaymentView: View {
@StateObject private var viewModel = PaymentViewModel()
var body: some View {
List(viewModel.paymentMethods) { method in
Text(method.title)
}.task {
await viewModel.fetchPaymentMethods()
}.alert(item: $viewModel.error) { … }
}
}
Key Takeaways
1. In MVC, the Command Pattern helps encapsulate business logic in the Model, keeping the ViewController clean and focused on its primary responsibilities.
2. In MVVM, the Command Pattern allows the ViewModel to manage logic, enabling clear communication between the View and Model.
By using the Command Pattern with either architecture, you enhance modularity, testability, and maintainability.
🤔
Key Question: Notice that Model is same in both approaches? while in MVC the UI mapping takes place within the ViewController, while in MVVM in takes place within the ViewModel?
💡
Having it as such, decouples the View code from presentation logic, allowing for possibility of reusing it across different UI frameworks (be it UIKit, SwiftUI, macOS, watchOS, heck… even CLI if you want)
Final Thoughts
SwiftUI might feel like a fresh start, but without discipline, the same old traps of massive files and tangled logic await us. By adopting patterns like Command, we can keep our apps clean, maintainable, and a joy to work on.
What do you think? Have you used the Command Pattern in your projects? Let’s discuss in the comments below! 🚀