SwiftUI Observation Framework Explained - Using @Observable in iOS 17

January 19, 2026

SwiftUI’s rendering system is built around a single core idea: views are cheap to recreate, but state must be preserved. To support this model, SwiftUI needs a reliable way to observe changes in data and update only the parts of the UI that depend on those changes.

Prior to iOS 17, this responsibility was handled primarily by ObservableObject and @Published. While effective, that approach introduced boilerplate, relied heavily on Combine, and triggered view updates at the object level rather than the property level.

With iOS 17, Apple introduced the Observation framework, centered around the @Observable macro. This new system provides a lighter, more precise way to model observable state in SwiftUI.

This article explains how the Observation framework works, how to use @Observable correctly in SwiftUI, how it differs from ObservableObject, and how to migrate existing code safely.

Why Apple Introduced a New Observation System

Before iOS 17, observable state in SwiftUI typically looked like this:

class UserViewModel: ObservableObject {
    @Published var name: String = ""
    @Published var isPremium: Bool = false
}

This approach worked, but it had several drawbacks:

  • Boilerplate everywhere: Every observable type had to conform to ObservableObject, and every changing property required @Published.

  • Over-notification: A change to any @Published property invalidates all dependent views, even if they don’t use that property.

  • Manual correctness: Forgetting @Published silently breaks UI updates.

Apple’s answer to these problems is the Observation framework, which introduces a compiler-driven observation system.

Introducing @Observable

At the core of the new system is the @Observable macro.

@Observable
class User {
    var name: String = ""
    var isPremium: Bool = false
}

That’s it. No ObservableObject. No @Published.

Every stored property becomes observable by default.

Key Design Goals

Apple designed @Observable with three principles in mind:

  • Explicit at the type level: You mark what is observable, not how each property behaves.

  • Fine-grained dependency tracking: Views only re-render when the specific properties they read change.

  • Minimal runtime overhead: Observation metadata is generated at compile time.

How Observation Actually Works

This is where the new system truly shines.

Property-Level Dependency Tracking

With @Observable, SwiftUI tracks which properties are read during view evaluation.

struct ProfileView: View {
    @State private var user = User()

    var body: some View {
        Text(user.name)
    }
}

In this example:

  • SwiftUI records that ProfileView depends on user.name
  • Changing user.isPremium does not invalidate this view
  • Only updates to name trigger a re-render

This is a major improvement over ObservableObject, where any @Published change triggers objectWillChange.

Initializing Observable Models in SwiftUI

SwiftUI view structs are ephemeral and may be recreated frequently. For this reason, observable models must be initialized in stable storage.

Incorrect Initialization

struct SettingsView: View {
    private let settings = InvoiceSettingsModel()

    var body: some View {
        Text(settings.currencyCode)
    }
}

This may appear correct, but the model can be recreated whenever the view is reinitialized.

Correct Initialization Using @State

Observable models should be initialized using @State.

import SwiftUI
import Observation

@Observable
class InvoiceSettingsModel {
    var currencyCode: String = "USD"
    var includeTax: Bool = false
}

struct SettingsView: View {
    @State private var model = InvoiceSettingsModel()

    var body: some View {
        Form {
            Toggle("Include Tax", isOn: $model.includeTax)
            Text("Currency: \(model.currencyCode)")
        }
    }
}

Here:

  • @State guarantees stable model lifetime
  • @Observable enables fine-grained updates
  • SwiftUI updates only when accessed properties change

To understand why an @Observable class must be initialized using @State within the SwiftUI hierarchy, refer to this excellent article by Natalia Panferova:
Initializing @Observable classes within the SwiftUI hierarchy

Excluding Properties from Observation

Not every property should trigger UI updates.

@Observable
class AnalyticsModel {
    var visibleCount = 0

    @ObservationIgnored
    var internalCache: [String: Int] = [:]
}

Use @ObservationIgnored to exclude properties that:

  • Are derived or transient
  • Don’t affect UI rendering
  • Are performance-sensitive

Comparison: Old vs New Observation

Old (ObservableObject)

class ViewModel: ObservableObject {
    @Published var title: String = ""
    @Published var isLoading = false
}
struct ContentView: View {
    @ObservedObject var model = ViewModel()

    var body: some View {
        Text(model.title)
    }
}

New (@Observable)

@Observable
class ViewModel {
    var title: String = ""
    var isLoading = false
}
struct ContentView: View {
    @State private var model = ViewModel()

    var body: some View {
        Text(model.title)
    }
}

Key Differences

Aspect ObservableObject @Observable
Boilerplate High Minimal
Property annotations Required Automatic
Dependency tracking Object-wide Property-level
Compile-time safety Low High

Performance and Predictability

Because dependencies are tracked at the property level:

  • Views re-render less often
  • Complex screens scale better
  • Large observable models no longer cause global invalidation

This makes the Observation framework particularly useful for:

  • Settings screens
  • Dashboards
  • Forms with many fields
  • State-heavy business apps

Migration Strategy

You don’t need to migrate everything at once.

Recommended Approach

  • Keep existing ObservableObject models working
  • Introduce @Observable for new features
  • Gradually refactor models with heavy @Published usage
  • Remove @ObservedObject where possible

SwiftUI fully supports mixing both systems.

Common Pitfalls

  • Mutating outside the main actor: Observation does not automatically enforce threading rules.
  • Assuming all mutations trigger updates: Only observed properties that are read by views cause re-renders.
  • Over-modeling: Don’t make everything observable—prefer smaller, focused models.

When Should You Use @Observable?

Use it when:

  • Targeting iOS 17+
  • You want cleaner state management
  • You care about fine-grained view updates

Avoid it if:

  • You must support older OS versions
  • You rely heavily on Combine publishers

Final Thoughts

The Observation framework is not just a syntactic improvement—it’s a fundamental shift in how SwiftUI tracks state changes.

By moving observation to the compiler level, Apple has made SwiftUI:

  • More efficient
  • More predictable
  • Easier to reason about

If you’re building modern SwiftUI apps targeting iOS 17 and later, @Observable should be your default choice for shared state.

Thank you for reading. If you have any questions feel free to follow me on X and send me a DM. If you enjoyed this article and would like to support me, Buy me a coffee.