Transitioning to the New Observable Macro in SwiftUI

Garrett Barker
4 min readJun 17, 2023

--

Photo by Chelsea Scott on Unsplash

Starting with iOS 17, iPadOS 17, macOS 14, tvOS 17, and watchOS 10, SwiftUI introduces Observation, a powerful Swift-specific implementation of the observer design pattern. By adopting Observation, you can unlock several benefits, such as tracking optionals and collections, using existing data flow primitives like State and Environment, and improving your app’s performance by updating views based on changes to specific observable properties. In this article, we’ll explore how to transition your existing codebase from using the ObservableObject protocol to the new Observable macro, enabling you to leverage the power of Observation in your SwiftUI apps.

Using the Observable Macro

To begin adopting Observation in your app, start by importing the Observation framework and replacing the usage of ObservableObject with the Observable macro in your data model types. The Observable macro generates compile-time code that adds observation support to the specified type. Here’s an example of how to make this transition:

// BEFORE
import SwiftUI

class Library: ObservableObject {
// ...
}

// AFTER
import SwiftUI
import Observation

@Observable class Library {
// ...
}

After applying the Observable macro, you can remove the Published property wrapper from the observable properties in your data model. Unlike ObservableObject, Observation doesn’t require a property wrapper to make a property observable. The accessibility of the property in relation to an observer, such as a view, determines its observability. Here’s an example:

// BEFORE
@Observable class Library {
@Published var books: [Book] = [Book(), Book(), Book()]
}

// AFTER
@Observable class Library {
var books: [Book] = [Book(), Book(), Book()]
}

If there are properties that you don’t want to track and are accessible to an observer, you can apply the ObservationIgnored macro to those specific properties.

Migrating Incrementally

Transitioning to the new Observable macro doesn’t require a wholesale replacement of the ObservableObject protocol throughout your app. You can make changes incrementally, starting with one data model type. SwiftUI supports mixing data model types that use different observation systems, Observable and ObservableObject. However, note that SwiftUI tracks changes differently based on the observation system used by a data model type.

When using Observable, SwiftUI updates a view only when an observable property changes and the view’s body reads that property directly. In contrast, with ObservableObject, a view updates when any published property of an ObservableObject instance changes, even if the view doesn’t read the changing property. Be aware of these behavioral differences when migrating your codebase.

Migrating Other Source Code

After applying the Observable macro to your data model types, you can continue migrating other source code in your app. While SwiftUI still supports data flow property wrappers like StateObject, you can fully adopt Observation by replacing them with State after updating your data model types. For example:

// BEFORE
@main
struct BookReaderApp: App {
@StateObject private var library = Library()

var body: some Scene {
WindowGroup {
LibraryView()
.environmentObject(library)
}
}
}

// AFTER
@main
struct BookReaderApp: App {
@State private var library = Library()

var body: some Scene {
WindowGroup {
LibraryView()
.environment(library)
}
}
}

In addition to replacing StateObject with State, update the usage of EnvironmentObject with Environment. For instance:

// BEFORE
struct LibraryView: View {
@EnvironmentObject var library: Library

var body: some View {
List(library.books) { book in
BookView(book: book)
}
}
}

// AFTER
struct LibraryView: View {
@Environment(Library.self) private var library

var body: some View {
List(library.books) { book in
BookView(book: book)
}
}
}

Finally, remove the ObservedObject property wrapper from your views. In most cases, it’s no longer needed when using Observation because SwiftUI automatically tracks observable properties that a view’s body reads directly. Here’s an example:

// BEFORE
struct BookView: View {
@ObservedObject var book: Book

var body: some View {
// View code...
}
}

// AFTER
struct BookView: View {
var book: Book

var body: some View {
// View code...
}
}

However, if a view requires a binding to an observable type, replace the ObservedObject property wrapper with the Bindable property wrapper. The Bindable property wrapper provides binding support to an observable type, allowing views to modify observable properties. Here’s an example:

// BEFORE
struct BookEditView: View {
@ObservedObject var book: Book

var body: some View {
// View code...
}
}

// AFTER
struct BookEditView: View {
@Bindable var book: Book

var body: some View {
// View code...
}
}

By following these steps, you can successfully transition your existing codebase to leverage the new Observable macro and embrace the power of Observation in your SwiftUI apps.

Conclusion

In this article, we explored the process of transitioning from using the ObservableObject protocol to the new Observable macro in SwiftUI. By adopting Observation, you can take advantage of improved performance, simplified code, and enhanced control over your data models. Remember to make incremental changes, update other source code, and remove unnecessary property wrappers to fully embrace Observation. With these steps, you’ll be well on your way to unlocking the full potential of SwiftUI’s Observation feature in your app development journey. Happy coding!

Thank you for reading! Please 👏 clap, leave a comment and follow I really appreciate it! It helps me to know what content people are interested in.

The information in this article was taken from https://developer.apple.com/documentation/swiftui/migrating-from-the-observable-object-protocol-to-the-observable-macro

--

--

Garrett Barker

Software Engineer, Technology, SwiftUI, iOS, Programing, Mobile Developer