What is @ObservableObject in SwiftUI

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.