Files
2nd/10_Wiki/Topics/Coding/iOS_SwiftUI_State_Property_Wrappers.md
T
2026-05-09 21:08:02 +09:00

3.8 KiB


id: ios-swiftui-state-property-wrappers title: SwiftUI State Property Wrappers — 어떤 걸 언제 category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [ios, swiftui, state, property-wrapper, vibe-coding] tech_stack: { language: "Swift 5.7+ / SwiftUI 4+", applicable_to: ["iOS 16+", "macOS 13+"] } applied_in: [] aliases: [@State, @Binding, @StateObject, @ObservedObject, @EnvironmentObject, @Observable]

SwiftUI State Property Wrappers

잘못 고르면 view identity 문제 + 의도치 않은 재생성. 핵심: 소유자 = @State / @StateObject / @Observable, 차용자 = @Binding / @ObservedObject / Bindable, 트리 전역 = @Environment(Object).

📖 핵심 개념

  • iOS 17+ 권장: @Observable 매크로 + let/var 일반 프로퍼티. 정밀 추적.
  • iOS 16-: ObservableObject + @Published + @StateObject / @ObservedObject.

💻 코드 패턴

iOS 17+ — @Observable 권장

@Observable
final class CartModel {
    var items: [Item] = []
    var total: Decimal = 0
}

struct CartView: View {
    @State private var cart = CartModel() // 소유자
    var body: some View {
        Text("\(cart.total)")
        ChildView(cart: cart) // 그냥 전달
    }
}

struct ChildView: View {
    let cart: CartModel // 차용자 — 별도 wrapper 불필요 (Observation 자동)
    @Bindable var cartBindable: CartModel // 양방향 binding 필요시
    var body: some View {
        TextField("name", text: $cartBindable.note)
    }
}

iOS 16- — ObservableObject

final class CartModel: ObservableObject {
    @Published var items: [Item] = []
}

struct CartView: View {
    @StateObject private var cart = CartModel() // 소유자
    var body: some View { ChildView(cart: cart) }
}

struct ChildView: View {
    @ObservedObject var cart: CartModel // 차용자
}

Environment 주입

struct AppView: View {
    @State private var user = UserModel()
    var body: some View {
        ContentView()
            .environment(user) // iOS 17+
            // .environmentObject(user) // iOS 16-
    }
}

struct DeepChild: View {
    @Environment(UserModel.self) var user // iOS 17+
    // @EnvironmentObject var user: UserModel // iOS 16-
}

Binding 양방향

struct Toggle: View {
    @Binding var isOn: Bool
    var body: some View { Button(isOn ? "on" : "off") { isOn.toggle() } }
}

// 호출
@State var on = false
Toggle(isOn: $on)

🤔 의사결정 기준

역할 iOS 17+ iOS 16-
값 타입 (Bool, String, struct) 소유 @State @State
클래스 모델 소유 @State (Observable) @StateObject
클래스 모델 차용 일반 let @ObservedObject
양방향 binding 차용 @Bindable @Binding (값 타입만)
트리 전역 @Environment(Type.self) @EnvironmentObject

안티패턴

  • 부모-자식 모두 @StateObject: 자식 view 가 재생성되면 새 instance. 자식은 @ObservedObject 또는 받기.
  • @StateObject var x = makeBigThing(): init 매번 호출 (실제 사용 안 됨). 가벼운 init 권장.
  • @ObservedObject 를 owner 처럼 사용: parent rebuild 시 자식 instance 새로 생성 → state 잃음.
  • iOS 17+ 코드에 @Published, ObservableObject 혼재: 일관성. 한 모델은 한 패턴.
  • EnvironmentObject 주입 안 하고 사용: 런타임 crash. 모델 매크로 / preview 에서도 주입.
  • 값 타입 큰 struct 를 @State 로 자주 mutate: copy 비용. 클래스 + Observable.

🤖 LLM 활용 힌트

  • iOS 17+ vs 16- 명시 후 코드 작성.
  • Preview 에서도 EnvironmentObject 주입 코드 같이.

🔗 관련 문서