@ObservedObject is a property wrapper in SwiftUI that allows a View to observe changes in an ObservableObject created elsewhere in the hierarchy. Unlike @StateObject, @ObservedObject does not create the object — it only subscribes to its objectWillChange publisher and redraws the View when published properties update. This makes @ObservedObject the right choice for child Views that receive data from a parent through an initializer. According to an article by Paul Hudson — Hacking with Swift (2025), a typical SwiftUI application architecture is built like this: the root View uses @StateObject to create a view model, and all child Views receive it through @ObservedObject, ensuring a single source of truth without data duplication.
Key Takeaways
@ObservedObject is a property wrapper that subscribes a View to ObservableObject changes. When an object marked with @ObservedObject changes any of its properties declared with @Published, SwiftUI automatically redraws the View. @ObservedObject does not create the object — it only establishes a connection between an existing ObservableObject instance and the View that should react to its changes.
The key difference between @ObservedObject and @StateObject is ownership. @ObservedObject assumes the object is created and stored somewhere higher in the View hierarchy. The child View receives a reference to this object through the initializer and simply observes it. If the child View is recreated, it receives the same reference from the parent — data is not lost.
According to Apple Developer Documentation — SwiftUI (2025), @ObservedObject is available starting with iOS 13, making it the only option for observing ObservableObject in projects supporting older iOS versions. In iOS 14+, @StateObject is preferred for creating objects, but @ObservedObject remains relevant for passing existing objects.
The @ObservedObject mechanism is based on the ObservableObject protocol from the Combine framework. Each class conforming to ObservableObject automatically gets an objectWillChange publisher that sends a signal before any @Published property changes. SwiftUI subscribes to this publisher through @ObservedObject and upon receiving the signal marks the View as needing a redraw.
class TaskViewModel: ObservableObject {
@Published var tasks: [Task] = []
@Published var isLoading = false
func loadTasks() async {
isLoading = true
// fetch data
isLoading = false
}
}
struct TaskListView: View {
@ObservedObject var viewModel: TaskViewModel
var body: some View {
List(viewModel.tasks) { task in
Text(task.title)
}
.task { await viewModel.loadTasks() }
}
}
When the parent View passes viewModel to TaskListView through the initializer, SwiftUI creates a connection between the object and the View. When the tasks array or isLoading flag changes, SwiftUI redraws TaskListView. The object itself remains unchanged — it is stored in the parent View through @StateObject.
The difference between @ObservedObject and @StateObject is the difference between an observer and an owner. @StateObject creates the object and manages its lifecycle. @ObservedObject only observes an object that was created and stored elsewhere. The choice between them is determined by the View’s responsibility for the data.
| Scenario | Recommendation | Reason |
|---|---|---|
| View creates data | @StateObject | View owns the object and is responsible for its lifecycle |
| View receives data | @ObservedObject | View only observes, the object lives in the parent |
| iOS 13 support | @ObservedObject | @StateObject is unavailable, use @ObservedObject with manual management |
| Reusable component | @ObservedObject | Component should not create data — it receives it externally |
The main rule: if the View creates the object — @StateObject. If the View receives the object — @ObservedObject. Violating this rule by using @ObservedObject to create an object leads to data loss when the View is rebuilt. Violating it by using @StateObject to receive an object creates a duplicate instance independent from the parent.
A typical usage scenario for @ObservedObject is a task list where the root View creates a view model and each list cell receives it through @ObservedObject. Each cell can call view model methods, and changes are automatically reflected across the entire list since all cells observe the same object.
struct TaskRow: View {
@ObservedObject var viewModel: TaskViewModel
let task: Task
var body: some View {
HStack {
Text(task.title)
Spacer()
Button("Done") {
viewModel.completeTask(task)
}
}
}
}
struct TaskListContainer: View {
@StateObject var viewModel = TaskViewModel()
var body: some View {
List(viewModel.tasks) { task in
TaskRow(viewModel: viewModel, task: task)
}
}
}
In this example, TaskListContainer creates viewModel through @StateObject, and each TaskRow receives it through @ObservedObject. When the user presses “Done” in any row, viewModel.completeTask changes a published property, and all Views observing this object update automatically.
The most common mistake is using @ObservedObject to create an object inside a View. When the View is rebuilt (e.g., when state changes), SwiftUI creates a new ObservableObject instance, leading to the loss of all accumulated data. This mistake is especially painful in NavigationStack, where a user might fill out a form and lose data when navigating back.
// ❌ Data loss: @ObservedObject does not retain object
struct FormView: View {
@ObservedObject var formVM = FormViewModel()
// New formVM created on each View rebuild!
}
// ✅ Correct: @StateObject retains the object
struct FormView: View {
@StateObject var formVM = FormViewModel()
// Object created once per View lifetime
}
If a child View declares the same ObservableObject through @StateObject, it creates an independent copy. Changes in the parent object will not be visible in the child, and vice versa. Always use @ObservedObject for child Views that receive the object externally.
In modern SwiftUI there are several alternatives to @ObservedObject, each with its own advantages. The choice depends on the application architecture, iOS version, and the specific use case.
The choice between @ObservedObject and @EnvironmentObject is a matter of style and architecture. @ObservedObject explicitly shows View dependencies through the initializer, making the code more predictable. @EnvironmentObject is convenient for deep hierarchies but hides dependencies, which can complicate debugging.
Frequently Asked Questions
Yes, if the object is created and stored outside SwiftUI — for example, in an AppDelegate or singleton. In this case, @ObservedObject simply subscribes to changes of an existing object. However, for objects created inside the SwiftUI hierarchy, @StateObject is always needed somewhere above.
The most likely reason is that the property is changed not through @Published or the object itself is not changed but its internal structure is mutated without calling objectWillChange. For collections, use assignment of a new copy: array.append() is not enough — you need to reassign the array itself through array = array + [element].
@ObservedObject itself does not create significant overhead. Problems arise with frequent changes to @Published properties — each change triggers a redraw of all observing Views. For optimization, use EquatableView, reduce the number of published properties, and avoid unnecessary updates.
@ObservedObject observes an entire ObservableObject class and redraws the View on any change to its published properties. @Binding creates a two-way connection to a specific value (String, Int, Bool) and allows reading and writing it. @Binding is lighter and does not require ObservableObject.
Yes, this is the standard pattern. @Published inside ObservableObject automatically integrates with @ObservedObject. Each @Published property adds an observer to the objectWillChange publisher. When any of them changes, all Views with @ObservedObject for this object are redrawn.
Summary
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.