Design Converter
Education
Software Development Executive - III
Last updated on Jan 1, 2025
Last updated on Jan 1, 2025
In SwiftUI, property wrappers like @ObservedObject and @StateObject play a crucial role in managing the state of your views. Both help SwiftUI views interact with data models, yet their use cases and lifecycles differ significantly.
Understanding these differences is essential to build apps that ensure consistent results, avoid common pitfalls, and make your views behave predictably.
This blog will explore the differences between these two property wrappers in-depth and show you when to use @ObservedObject vs. @StateObject.
Property wrappers, such as @StateObject
and @ObservedObject
, simplify how you manage and observe data in your views by working specifically with ObservableObject
instances. They integrate with SwiftUI's declarative architecture, helping to ensure that view updates are triggered automatically when your data changes. Let's first understand what these two wrappers do:
@ObservedObject
: Used for observing an ObservableObject
instance that is owned elsewhere. It does not manage the lifecycle of the object.
@StateObject
: Introduced in iOS 14, this property wrapper creates and owns the ObservableObject
instance, managing its lifecycle.
Both @ObservedObject
and @StateObject
work with classes conforming to the ObservableObject
protocol. This protocol allows you to mark a class as an observable entity that emits changes to SwiftUI views whenever properties marked with the @Published
attribute are updated.
1import SwiftUI 2import Combine 3 4// ObservableObject class with a published property 5class CounterViewModel: ObservableObject { 6 @Published var count = 0 // This property notifies views of changes. 7 8 func incrementCounter() { 9 count += 1 // Increment the count and trigger updates to any observing views. 10 } 11}
The CounterViewModel
class above tracks a count
property and updates any views observing it.
@ObservedObject
: When to Use ItThe @ObservedObject
property wrapper is ideal when you want to observe an ObservableObject
instance passed from a parent view or a shared object in your app’s environment. Since it doesn't manage the lifecycle of the object, the parent view is the source of truth.
1struct ChildView: View { 2 @ObservedObject var counter: CounterViewModel 3 4 var body: some View { 5 VStack { 6 Text("Count: \(counter.count)") 7 Button("Increment") { 8 counter.incrementCounter() 9 } 10 } 11 } 12}
@ObservedObject
when the object is already created in a parent view or is a shared object.@StateObject
: When to Use ItThe @StateObject
property wrapper creates and owns the ObservableObject
instance. It ensures that the object persists for the view's entire lifecycle, even if the view is recreated or redraws due to state changes.
1struct ContentView: View { 2 @StateObject var counter = CounterViewModel() 3 4 var body: some View { 5 VStack { 6 Text("Count: \(counter.count)") 7 Button("Increment") { 8 counter.incrementCounter() 9 } 10 } 11 } 12}
@StateObject
when the current view creates the ObservableObject
.@StateObject
for shared objects, as this may create duplicate data sources.Using the wrong property wrapper can lead to bugs like unnecessary view updates or data inconsistencies. For example:
Using @ObservedObject
to create an object: The ObservableObject
will be re-instantiated each time the view redraws, which can occur frequently in SwiftUI due to state changes. This causes unexpected behaviors such as resetting counters.
Faulty Example:
1struct FaultyView: View { 2 @ObservedObject var counter = CounterViewModel() // Wrong property wrapper 3 4 var body: some View { 5 VStack { 6 Text("Count: \(counter.count)") 7 Button("Increment") { 8 counter.incrementCounter() 9 } 10 } 11 } 12}
Fixed Example:
1struct FixedView: View { 2 @StateObject var counter = CounterViewModel() // Correct property wrapper 3 4 var body: some View { 5 VStack { 6 Text("Count: \(counter.count)") 7 Button("Increment") { 8 counter.incrementCounter() 9 } 10 } 11 } 12}
Using @StateObject
for shared objects: This can result in duplicate data sources, breaking the app's intended functionality.
@ObservedObject
and @StateObject
Aspect | @ObservedObject | @StateObject |
---|---|---|
Lifecycle Management | Does not manage the lifecycle. | Manages the lifecycle. |
Use Case | Observes existing objects. | Creates and owns objects. |
Reinstantiation Risk | May reset data on re-render, e.g., when the view redraws. | Ensures consistent results by persisting objects. |
Multiple Views Support | Suitable for shared objects. | Best for isolated objects. |
Using these two property wrappers together in a parent-child view setup can provide a complete solution.
1struct ParentView: View { 2 @StateObject var counter = CounterViewModel() // Owns the object 3 4 var body: some View { 5 VStack { 6 ChildView(counter: counter) // Passes the object to a child view 7 } 8 } 9} 10 11struct ChildView: View { 12 @ObservedObject var counter: CounterViewModel // Observes the parent-owned object 13 14 var body: some View { 15 VStack { 16 Text("Count: \(counter.count)") 17 Button("Increment") { 18 counter.incrementCounter() 19 } 20 } 21 } 22}
In this example:
CounterViewModel
using @StateObject
.@ObservedObject
.Tired of manually designing screens, coding on weekends, and technical debt? Let DhiWise handle it for you!
You can build an e-commerce store, healthcare app, portfolio, blogging website, social media or admin panel right away. Use our library of 40+ pre-built free templates to create your first application using DhiWise.