간단 요약(외워두면 편할 듯)
- @State: 뷰 "자기 자신"만 쓰는 작은 값(숫자, 문자열 등) - 수명 = 해당 뷰
- @StateObject: 어떤 참조 타입 모델을 "내가 생성하고 소유"할 때. - 수명 = 해당 뷰
- @ObservedObject: 이미 다른 곳에서 소유중인 모델을 "구독만"할 때.
- @EnvironmentObject: 멀리 떨어진 여러 뷰에서 같은 모델을 쓰고 싶을 때. 반드시 상위에서 .environmentObject()로 주입해야 함.
- 바인딩 기호는 $ (예: Slider(value: @speedSetting.speed, ...))
언제 무엇을 쓰나(의사결정)
- 이 데이터, 이 뷰 안에서만 쓰고 끝 -> @State / @Binding
- 참조형 모델을 여기서 만들고 자식들에 내려줄 예정 -> 부모는 @StateObject, 자식은 @ObservedObject
- 뷰가 여기저기 많고 중간 단계 전부에 파라미터로 전달하기 귀찮다 -> @EnvironmentObject + 상위에서 .environmentObject() 주입
올바른 코드 패턴 3종
뷰 로컬 상태(@State)
struct CounterView: View { @State private var count = 0 var body: some View { Button("Count: \(count)") { count += 1 } } } - 이 뷰 안에서만 작동하는 경우
부모가 소유(@StateObject) -> 자식이 구독(ObservedObject)
final class DemoData: ObservableObject { @Published var name = "James" } struct FirstView: View { @StateObject private var demoData = DemoData() // 내가 생성 및 소유 var body: some View { NavigationStack { VStack { Text(demoData.name) NavigationLink("Next") { SecondView(demoData: demoData) // 자식으로 전달 } } } } } struct SecondView: View { @ObservedObject var demoData: DemoData // 전달받은 걸 구독만 var body: some View { Text("Hello, \(demoData.name)") } } - NavigationLink(destination: SecondView(demoData))는 보통 SecondView(demoData: demoData)처럼 파라미터 라벨을 붙이는 게 일반적이다.
여러 화면에서 공유(@EnvironmentObject)
final class SpeedSetting: ObservableObject { @Published var speed = 0.0 } // 가장 안전: App루트에서 주입 @main struct MyApp: App { @StateObject private var speedSetting = SpeedSetting() var body: some Scene { WindowGroup { ContentView() .environmentObject(speedSetting) // 여기서 한 번 주입 } } } struct ContentView: View { var body: some View { VStack { SpeedControlView() SpeddDisplayView() } } } struct SpeedControlView: View { @EnvironmentObject var speedSetting: SpeedSetting var body: some View { Slider(value: $speedSetting.speed, in: 0...100) // 바인딩은 $ } } struct SpeedDisplayView: View { @EnvironmentObject var speedSetting: SpeedSetting var body: some View { Text("Speed = \(Int(speedSetting.speed))") } } [!note] 음... 한 70% 이해한 것 같다. 이건 계속 반복해서 쓰다보면 더 익숙해질 것 같긴 함.
왜 ContentView에서 let speedSetting = SpeedSetting()이 위험한가?
- View는 값 타입(Struct)이고, 재생성이 자주 된다.
- 그때마다 let speedSetting = SpeedSetting()이 새 인스턴스를 만들 수 있어 상태가 리셋될 위험이 있음.
- 그래서 App 루트에서 @StateObject로 만들고 .environmentObject로 한 번만 주입하는 게 가장 안전/예측 가능하다.
Navigation으로 전달 vs Environment로 공유
- 두세 화면 정도, 계층 가까움: 그냥 파라미터로 전달
- @ObservedObject -> 의존성이 명시적이라 추적이 쉽다.
- 여러 곳에서 공통 사용, 화면 사이 멀다/많다: @EnvironmentObject로 의존성 주입 -> 프로퍼티 드릴링 줄이기.
- 너무 남발하면 "어디서 들어왔는지"보기가 어려워지니, 설정/세션/권한/테마같은 진짜 공용 상태 위주로 쓰는 걸 추천
자주 나는 실수들
- @StateObject를 자식에서 새로 만들면? -> 화면 전환 때마다 새 모델 생김(원치 않는 리셋)
- @EnvironmentObject 주입 안 하고 사용? -> 런타임 크래시
- 바인딩에 $ 빠짐? -> 컴파일 에러(타입 미스매치)
- ObservableObject는 클래스여야 함(구조체 아님)
- 이름/대소문자 섞임
마지막 한 줄 요약
- 소유는 @StateObject, 소비는 @ObservedObject, 공용은 @EnvironmentObject
- UI값엔 @State/@Binding(바인딩은 $)