Flo Writes Code
← Back to Blog

Stop Using ObservableObject

ObservableObject introduces side effects and performance problems, but there's an alternative.

ObservableObject used to be "the only" solution to build ViewModels and other data sources for SwiftUI Views. For several years, the main question was which PropertyWrapper to use: ObservedObject, StateObject or EnvironmentObject. No matter which approach you chose, all of them relied on Combine for View updates - your ObservableObject had a objectWillChange object of type ObjectWillChangePublisher that notified Views about any @Published property changes.

The Problem

As always, the devil is in the details: This publisher fires every time any published property changes - no matter whether the View depends on that property or not. In many simple use cases, this probably doesn't matter. But once you break down your Views into smaller Subviews or start sharing an ObservableObject across multiple Views, things start to become problematic: There's obvious performance problems if the View redraws unnecessarily. All of this is happening, because ObservableObject is built (and connected to your Views) with the Combine framework.

The solution

First things first: you can stop reading this article, if your app needs to support pre-iOS 17 (or heavily relies on Combine pipelines for your published properties), because that's when the framework we're about to look into was introduced. It's called Observation and is a (almost) direct successor to ObservableObject. The framework contains a new @Observable macro you can attach to a Swift class.

// Old approach
import SwiftUI

class ViewModel: ObservableObject {
  @Published var username: String = ""
}

// New approach
import SwiftUI
import Observation

@Observable
class ViewModel {
  var username: String = ""
}

If you've got an eye for details, you'll notice that there's a fundamental change: Implicit State. You used to explicitly mark properties as @Published. Any non-marked property wouldn't update the View. But with Observation, any property is updating. You can mark a property as non-updating by attaching the @ObservationIgnored macro.

I started this article by talking about the different PropertyWrappers to attach an ObservableObject to a View. Those don't work with @Observable classes. Instead, you'll just be using @State for classes owned by this View and @Bindable or @Environment for classes owned by another View.