3.8 KiB
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 주입 코드 같이.