forked from steipete/agent-rules
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathswift-observable.mdc
More file actions
271 lines (220 loc) · 7.61 KB
/
swift-observable.mdc
File metadata and controls
271 lines (220 loc) · 7.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
---
description: Migration guide for updating SwiftUI apps from ObservableObject to the @Observable macro
globs: "**/*.swift"
alwaysApply: false
---
Here is the content from the Apple Developer documentation page "Migrating from the Observable Object protocol to the Observable macro" converted to Markdown format.
# Migrating from the Observable Object protocol to the Observable macro
Update your existing app to leverage the benefits of Observation in Swift.
***
## Overview
Starting with iOS 17, iPadOS 17, macOS 14, tvOS 17, and watchOS 10, SwiftUI provides support for Observation, a Swift-specific implementation of the observer design pattern. Adopting Observation provides your app with the following benefits:
> * Tracking optionals and collections of objects, which isn't possible when using `ObservableObject`.
> * Using existing data flow primitives like `State` and `Environment` instead of object-based equivalents such as `StateObject` and `EnvironmentObject`.
> * Updating views based on changes to the observable properties that a view's body reads instead of any property changes that occur to an observable object, which can help improve your app's performance.
To take advantage of these benefits in your app, you'll discover how to replace existing source code that relies on `ObservableObject` with code that leverages the `@Observable()` macro.
***
## Use the Observable macro
To adopt Observation in an existing app, begin by replacing `ObservableObject` in your data model type with the `@Observable()` macro. The `@Observable()` macro generates source code at compile time that adds observation support to the type.
**BEFORE**
```swift
// BEFORE
import SwiftUI
class Library: ObservableObject {
// ...
}
```
**AFTER**
```swift
// AFTER
import SwiftUI
@Observable
class Library {
// ...
}
```
Then remove the `@Published` property wrapper from observable properties. Observation doesn't require a property wrapper to make a property observable. Instead, the accessibility of the property in relationship to an observer, such as a view, determines whether a property is observable.
**BEFORE**
```swift
// BEFORE
@Observable
class Library {
@Published var books: [Book] = [Book(), Book(), Book()]
}
```
**AFTER**
```swift
// AFTER
@Observable
class Library {
var books: [Book] = [Book(), Book(), Book()]
}
```
If you have properties that are accessible to an observer that you don't want to track, apply the `@ObservationIgnored()` macro to the property.
***
## Migrate incrementally
You don't need to make a wholesale replacement of the `ObservableObject` protocol throughout your app. Instead, you can make changes incrementally. Start by changing one data model type to use the `@Observable()` macro. Your app can mix data model types that use different observation systems. However, SwiftUI tracks changes differently based on the observation system that a data model type uses, `Observable` versus `ObservableObject`.
You may notice slight behavioral differences in your app based on the tracking method. For instance, when tracking as `@Observable()`, SwiftUI updates a view only when an observable property changes and the view's body reads the property directly. The view doesn't update when observable properties not read by body changes. In contrast, a view updates when any published property of an `ObservableObject` instance changes, even if the view doesn't read the property that changes, when tracking as `ObservableObject`.
> **Note:** To learn more about when SwiftUI updates views when observable properties change, see [Managing model data in your app](https://developer.apple.com/documentation/swiftui/managing-model-data).
***
## Migrate other source code
After applying the `@Observable()` macro, you can update your data flow primitives. Although `StateObject` and `EnvironmentObject` support types using the `@Observable()` macro for incremental migration, you should replace them with `State` and `Environment` to fully adopt Observation.
**BEFORE**
```swift
// BEFORE
@main
struct BookReaderApp: App {
@StateObject private var library = Library()
var body: some Scene {
WindowGroup {
LibraryView()
.environmentObject(library)
}
}
}
```
**AFTER**
```swift
// AFTER
@main
struct BookReaderApp: App {
@State private var library = Library()
var body: some Scene {
WindowGroup {
LibraryView()
.environment(library)
}
}
}
```
Similarly, replace `@EnvironmentObject` with the `@Environment` property wrapper.
**BEFORE**
```swift
// BEFORE
struct LibraryView: View {
@EnvironmentObject var library: Library
var body: some View {
List(library.books) { book in
BookView(book: book)
}
}
}
```
**AFTER**
```swift
// AFTER
struct LibraryView: View {
@Environment(Library.self) private var library
var body: some View {
List(library.books) { book in
BookView(book: book)
}
}
}
```
***
## Remove the ObservedObject property wrapper
To complete the migration, change other data model types to support Observation by replacing `ObservableObject` with the `@Observable()` macro and removing `@Published`.
**BEFORE**
```swift
// BEFORE
class Book: ObservableObject, Identifiable {
@Published var title = "Sample Book Title"
let id = UUID() // A unique identifier that never changes.
}
```
**AFTER**
```swift
// AFTER
@Observable
class Book: Identifiable {
var title = "Sample Book Title"
let id = UUID() // A unique identifier that never changes.
}
```
Next, remove the `@ObservedObject` property wrapper. SwiftUI automatically tracks any observable properties that a view's body reads directly.
**BEFORE**
```swift
// BEFORE
struct BookView: View {
@ObservedObject var book: Book
@State private var isEditorPresented = false
var body: some View {
HStack {
Text(book.title)
Spacer()
Button("Edit") {
isEditorPresented = true
}
}
.sheet(isPresented: $isEditorPresented) {
BookEditView(book: book)
}
}
}
```
**AFTER**
```swift
// AFTER
struct BookView: View {
var book: Book
@State private var isEditorPresented = false
var body: some View {
HStack {
Text(book.title)
Spacer()
Button("Edit") {
isEditorPresented = true
}
}
.sheet(isPresented: $isEditorPresented) {
BookEditView(book: book)
}
}
}
```
If a view needs a binding to an observable type, replace `@ObservedObject` with the `@Bindable` property wrapper.
**BEFORE**
```swift
// BEFORE
struct BookEditView: View {
@ObservedObject var book: Book
@Environment(\.dismiss) private var dismiss
var body: some View {
VStack() {
TextField("Title", text: $book.title)
.textFieldStyle(.roundedBorder)
.onSubmit {
dismiss()
}
Button("Close") {
dismiss()
}
.buttonStyle(.borderedProminent)
}
.padding()
}
}
```
**AFTER**
```swift
// AFTER
struct BookEditView: View {
@Bindable var book: Book
@Environment(\.dismiss) private var dismiss
var body: some View {
VStack() {
TextField("Title", text: $book.title)
.textFieldStyle(.roundedBorder)
.onSubmit {
dismiss()
}
Button("Close") {
dismiss()
}
.buttonStyle(.borderedProminent)
}
.padding()
}
}
```