After talking about @Observable so much, I realize I should properly explain @ObservableObject, the pattern I lived with for years before Apple introduced the newer macro. Even though @Observable is the future, understanding @ObservableObject is still crucial since it’s what powers most existing SwiftUI apps and will continue to be relevant for years to come.
What @ObservableObject Actually Is
@ObservableObject is a protocol that I apply to classes to make them observable by SwiftUI views. Unlike the newer @Observable macro, it requires me to explicitly mark which properties I want SwiftUI to watch by using the @Published property wrapper.
Here’s how I typically set up an observable object:
import Combine
class UserSettings: ObservableObject {
@Published var isDarkMode = false
@Published var username = ""
@Published var notificationEnabled = true
// This property won't trigger view updates
var internalCounter = 0
}
The key difference from @Observable is that I have to explicitly decide which properties should trigger view updates by marking them with @Published.
How I Use @ObservableObject in My Views
Working with @ObservableObject requires different property wrappers depending on ownership:
When My View Owns the Object
struct SettingsView: View {
@StateObject private var settings = UserSettings()
var body: some View {
Toggle("Dark Mode", isOn: $settings.isDarkMode)
}
}
I use @StateObject when my view creates and owns the observable object. This ensures SwiftUI manages the object’s lifecycle properly.
When My View Receives the Object
struct SettingsDetailView: View {
@ObservedObject var settings: UserSettings
var body: some View {
VStack {
Text("User: \(settings.username)")
Toggle("Notifications", isOn: $settings.notificationEnabled)
}
}
}
I use @ObservedObject when another view passes me the observable object. This creates a dependency without claiming ownership.
The Key Limitations I’ve Experienced
The @Published Requirement
The biggest pain point with @ObservableObject is remembering to mark properties with @Published. I can’t count how many times I spent debugging why my views weren’t updating, only to realize I forgot to add @Published to a property.
Unnecessary View Updates
Here’s where @ObservableObject really shows its age. When I have an object like this:
class AppState: ObservableObject {
@Published var userCount = 0
@Published var unrelatedData = ""
}
I can even subscribe to individual @Published properties outside of SwiftUI for custom logic, which isn’t as straightforward with @Observable.
If unrelatedData changes, every view observing this AppState gets redrawn, even if that view only uses userCount. This can cause significant performance issues in complex apps.
Combine Dependency
@ObservableObject requires importing the Combine framework, which adds a dependency that isn’t always necessary for simple observable behavior.
Property Wrapper Confusion
One thing that tripped me up initially was the relationship between @StateObject and @ObservedObject. Here’s my mental model:
- @StateObject = “I own this object, I’m responsible for its lifecycle”
- @ObservedObject = “Someone else owns this object, I’m just borrowing it”
Using @ObservedObject when you should use @StateObject can cause memory leaks and inconsistent behavior.
Working with @Published Properties
The @Published property wrapper creates a publisher that emits whenever the property changes. This is powerful for complex data flows:
Performance Characteristics
The performance story with @ObservableObject is straightforward but not always optimal. When any @Published property changes, SwiftUI invalidates all views observing that object. For simple apps, this works fine, but for complex apps with lots of shared state, it can become a bottleneck.
Migration Considerations
While @Observable is clearly the future, I haven’t rushed to migrate all my existing @ObservableObject code. The old system works reliably, and migration requires careful testing to ensure behavior doesn’t change unexpectedly.
For new projects targeting iOS 17+, I use @Observable exclusively. But for existing projects or when I need to support older iOS versions, @ObservableObject remains a solid choice.
class NetworkManager: ObservableObject {
@Published var isLoading = false
@Published var userData: User?
@Published var errorMessage: String?
func fetchUser() {
isLoading = true
// Network call...
}
}
Why I Still Appreciate @ObservableObject
Despite its limitations, @ObservableObject served the SwiftUI community well for years. It provided a clear, explicit way to make objects observable, and the @Published requirement, while sometimes annoying, made data flow very explicit and debuggable.
