These property wrappers all tell SwiftUI and . The key split is (, ) versus (, , ), plus who is responsible for the object's lifetime.
@State@Binding@StateObject@ObservedObject@EnvironmentObject// Value state OWNED by this view
struct Toggle: View {
@State private var isOn = false // local source of truth
var body: some View {
Switch(isOn: $isOn) // $ makes a Binding
}
}
// A two-way reference to state owned ELSEWHERE
struct Switch: View {
@Binding var isOn: Bool // no ownership; reads+writes parent
var body: some View { Button("\(isOn)") { isOn.toggle() } }
}
final class CartModel: ObservableObject {
@Published var items: [Item] = [] // change => views re-render
}
struct CartScreen: View {
@StateObject private var model = CartModel() // OWNS: created once, survives re-renders
var body: some View { CartList(model: model) }
}
struct CartList: View {
@ObservedObject var model: CartModel // observes; owned by a parent
var body: some View { Text("\(model.items.count)") }
}
| Wrapper | Type | Ownership | Use when |
|---|---|---|---|
@State | value | this view | simple local state |
@Binding | value | a parent | child edits parent's state |
@StateObject | reference | this view (created once) | view creates the model |
@ObservedObject | reference | elsewhere | model passed in |
@EnvironmentObject | reference | ancestor | shared deep in the tree |
@StateObject vs @ObservedObject is the classic trap. Creating a model with @ObservedObject var m = Model() re-instantiates it on every re-render, silently losing state. Use @StateObject wherever the view creates the object.@EnvironmentObject crashes at runtime if no ancestor injected it via .environmentObject(...).Picking the wrong wrapper is the number-one source of SwiftUI state bugs — flickering values, resets on scroll, or views that never update. Interviewers ask this because the @StateObject vs @ObservedObject answer instantly reveals whether you understand SwiftUI's ownership model.
A library of IT interview questions with detailed answers — from Junior to Senior.
Donate