Skip to content

Migrate to @Observable for View Models #288

@Daggerpov

Description

@Daggerpov
Good questions. Short answer: yes — writing view models as class MyViewModel: ObservableObject { @Published … } is increasingly considered “old style.” With recent Swift / SwiftUI versions the trend is shifting toward using the newer @Observable macro (from the standard Observation framework) — but it’s not a strict “you must always switch.” How you mark your view models depends on your app’s deployment targets, complexity, and requirements. 
Apple Developer
+2
Use Your Loaf
+2

Here’s a detailed breakdown using your example:

✅ Why @Observable is now preferred (or often recommended)

The @Observable macro — available since Swift 5.9 / iOS 17+ / macOS 14+ — automatically synthesizes observation support for a class. You don’t need to declare conformance to ObservableObject, nor mark each property with @Published. 
Use Your Loaf
+2
SwiftLee
+2

The resulting observation is property-level, not “object-level.” This means only when a property actually used by a view changes will SwiftUI re-evaluate that part of the UI. That tends to reduce unnecessary view redraws and improves performance. 
flyingharley.dev
+2
Medium
+2

It reduces boilerplate — fewer wrappers, less Combine scaffolding (objectWillChange, @Published, etc.). 
flyingharley.dev
+2
Medium
+2

According to migration guides from Apple and community posts, @Observable is essentially the future of SwiftUI model-state design. 
Apple Developer
+2
Use Your Loaf
+2

Because of these benefits, many developers are shifting “view models / models” from ObservableObject + @Published → @Observable.

⚠️ But @Observable isn’t a perfect drop-in — caveats you should be aware of

It requires iOS 17 / macOS 14 / Swift 5.9+. If your app supports earlier OS versions (e.g. iOS 16 or older), @Observable isn’t an option unless you conditionally fall back or drop support. 
Medium
+1

Because @Observable doesn’t rely on Combine, some patterns — e.g. manual publishers, Combine pipelines, custom AnyCancellables — don’t directly translate. If your view model currently uses Combine for side-effects, debounce, cancellation, etc., migrating may require rethinking those parts. 
Stack Overflow
+1

For nested or hierarchical data models (models containing other models), you need to mark all relevant reference types with @Observable (or otherwise make them observable) if you want changes to propagate. 
Medium
+1

Concurrency / threading: though @Observable itself is thread-safe, when used with SwiftUI you still need to ensure that state updates occur on the main thread (e.g. via @MainActor). Otherwise, SwiftUI may “skip” updates or behave unpredictably. 
[Swift Forums](https://forums.swift.org/t/do-update-to-observable-properties-have-to-be-done-on-the-main-thread/74954?utm_source=chatgpt.com)

🔄 What you should do in your case (given the example you showed)

Given your ChatViewModel, here’s how I'd approach refactoring — and when I’d choose not to:

If your app deployment target is iOS 17+ only (or you’re OK dropping older support) → migrate to @Observable.

Change the class declaration to something like:

import Observation  // or import whatever module

@Observable
@MainActor  // you can still keep this if your view model is main-thread bound
final class ChatViewModel {
    // remove @Published
    var creationMessage: String?
    // other stored properties
}


Remove : ObservableObject, remove @Published.

In your SwiftUI view, instead of @StateObject / @ObservedObject, use @State (or plain let/var, if you know the lifetime) to hold the view model — because lifecycle-management for an @Observable object is up to you. 
Medium
+2
Donny Wals
+2

Keep @MainActor (if your view model performs UI-related updates) — that’s still sensible. The new Observation framework doesn’t replace the need to ensure UI-state mutations happen on the main thread. 
Swift Forums
+1

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    Status

    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions