MV pattern in SwiftUI

Aleksa Simic
September 1, 2024
5 min read

What is MV pattern in the first place?

For the past couple of months, there has been a big hype amongst the iOS community about the new MV (or how some people call it: State) pattern that should replace the MVVM in SwiftUI applications. Why? There are a couple of different reasons people advocating for MV say it's better than MVVM, but here are the two that are mentioned the most times: 

  1. SwiftUI views are already MVVM
  2. MVVM with SwiftUI is over engineering

Now, what's the idea behind MV pattern? The MV pattern can be broken down to two simple ideas: 

  1. Instead of using view models as a source of truth when it comes to view re-renders, we should rely on two things:
    •  Local @State variables - things which should trigger state re-render, and don't communicate to the outside world, for example, input selected state update, should be kept inside the View directly using the local @State variables. For comparison, in MVVM, these kind of things were being kept in the view model and being marked as @Published.
    • "Aggregate objects" - which are explained below and are used when the view/screen needs to communicate with the model layer, for example, trigger an API call
  1. For pieces of code which should access model objects, do API calls etc. we should use "aggregate" objects. The aggregate objects are objects which conform to ObservableObject or Observable macro (if you are supporting iOS 17.0 and above), they are shared between the multiple different views/screens and their main responsibility is to respond to some action from the View layer, do some work, and trigger screen re-renders. Simply put, you can understand them as something close to shared view models.
The idea of MV is to simplify and speed up the development of iOS applications using SwiftUI

MV architecture in practice on a real iOS project

At some point at the beginning of this year, me and my team at Aetherius solutions had a chance to start working on a new iOS project from scratch, and the tech lead on the project decided that we should support minimum iOS version of 17.0, so we thought that trying out MV would be good choice because we could use @Observable macro which works great with "aggregate models" from MV.

At start, everything was going pretty nice and smooth. But as soon as the project started growing, since we were a team of 4 iOS engineers constantly contributing to the codebase, thing started going downhill.

There are a couple of main reasons why that happened: 

  1. View/Screens slowly got bloated with a lot of local @State variables
  2. "Aggregate models" which were being shared between views/screens also got bloated because we were putting a lot of API related code on one place
  3. We started getting weird and unexpected re-renders on some screens because of the shared aggregate models
  4. A lot of merge conflicts because multiple engineers contributed to same aggregate models, while they were working on different screens
  5. We couldn't write unit tests for all pieces of the logic because a lot of things were in local @State variables

And I could add at least a couple of more items to this list.

At that point, we figured out that MV pattern just doesn't work that well for iOS apps that should be scalable and maintained by a bigger iOS team, and that we should probably try reverting back to MVVM.

Migrating back from MV to MVVM

After the decision to revert the whole application back to MVVM, we had to make a detailed plan on how to do it and not lose too much time, or break anything in the app.

The migration plan was the following: 

  1. Create view models for each screen/view
  2. Move all of the local @State variables from that screen/view to created view model as @Published properties
  3. Define what "aggregate model" holds the functionality that should be in which view model
  4. Move the defined functionalities from "aggregate models" to the view models

And that was pretty much it. Much easier than we expected. It took our 4 members iOS team about two weeks in total to do the whole migration without a single regression bug.

After that, when the codebase was back to MVVM, we started writing unit tests and just in general felt like we have more control over the whole project, while the development speed stayed the same.

Conclusion

I always like to try out new things, and if there is one thing I have learned over the past 7 years of being in iOS development, it is that you should always experiment and try out as many different things as you can.

Why? Because you can (almost) always revert things to how they were before and you will (almost always) learn something from that experiment.

And yeah, MVVM still rocks for SwiftUI, mostly when it comes to large iOS apps.

Aleksa Simic
Co-Founder & CEO of Aetherius Solutions
Share this post
Trending

The most popular e-books and project templates

iOS engineers from all over the world found these e-books and project templates as the ultimate source of knowledge needed to build and scale their mobile apps

Blog

Latest blogs

Kotlin Multiplatform
Kotlin Multiplatform vs Compose Multiplatform in building production ready mobile applications
iOS
Is MV pattern in SwiftUI ready to replace MVVM?
iOS
Step by step guide for creating feature module targets in iOS apps