본문 바로가기

SwiftUI 상태 관리 완전 정복기

@Prof.SSong2025. 8. 13. 10:50
728x90
반응형

안녕하세요. 오늘은 Apple Developer Academy @POSTECH 의 C5 학습 플랜의 첫날, 
SwiftUI 상태 관리에 대해 깊이 파고든 하루를 기록하려고 합니다.

저는 오늘 하루를 “상태 관리의 A부터 Z까지 손으로 부딪히며 확인하는 날”이라고 이름 붙였습니다.

 

 

오늘의 학습 목표

 

C5 플랜에서 Day 1의 목표는 이렇게 정의했습니다.

 

State, Binding, ObservedObject, StateObject, EnvironmentObject 개념과 동작 원리를 배우고, iOS 17+의 @Observable까지 실습한다.
각 방식의 메모리/라이프사이클 차이를 이해하고, 상황별 선택 이유를 명확하게 설명할 수 있게 한다 .

 

이 말은 곧, 단순히 문법을 아는 걸 넘어서 왜 이 방식을 써야 하는지, 그리고 이 방식이 아니면 어떤 문제가 생기는지를 체험으로 이해하자는 뜻입니다.

 


 

상태 관리란 무엇인가?

 

SwiftUI는 선언형 UI입니다.

즉, UI는 상태(State)의 함수입니다. 상태가 바뀌면, SwiftUI가 알아서 UI를 다시 그려줍니다.

 

문제는 상태를 어디서 어떻게 저장하고 공유하느냐입니다.

여기서 다양한 상태 관리 도구들이 등장합니다.

 


 

1. @State — 뷰 안에서만 쓰는 개인 메모장

 

  • 해당 View가 소유하는 지역 상태입니다.
  • View가 사라지면 상태도 함께 사라집니다.
  • 간단한 토글, 카운터, 입력값 저장에 적합합니다.
struct MyToggle: View {
    @State private var isOn = false
    
    var body: some View {
        Toggle("Switch", isOn: $isOn)
    }
}

 

특징

 

  • 값이 바뀌면 해당 View가 다시 렌더링됩니다.
  • 다른 뷰에서 이 값을 직접 소유할 수 없습니다.

 


 

2. @Binding — 원본과 연결만 하는 포스트잇

 

  • 저장소가 없습니다.
  • 부모의 상태를 링크 형태로 전달해, 자식이 수정할 수 있게 합니다.
struct Parent: View {
    @State private var isOn = false
    var body: some View {
        Child(isOn: $isOn) // 바인딩 전달
    }
}

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

 

특징

 

  • 원본 상태와 항상 동기화됩니다.
  • 상태 “소유”는 부모가 하고, 자식은 수정 권한만 받는 구조입니다.

 


 

3. @ObservedObject — 빌려 쓰는 참조

 

  • 클래스 기반 상태를 관찰합니다.
  • 부모가 새 인스턴스를 주입하면 교체됩니다 → 재렌더링 시 초기화 위험이 있습니다.
final class Counter: ObservableObject {
    @Published var count = 0
}

struct MyView: View {
    @ObservedObject var counter: Counter
    ...
}

 

특징

 

  • 뷰가 상태를 소유하지 않음 → 외부에서 주입받아 사용.
  • 소유권이 없기 때문에 재생성되면 값도 초기화됩니다.

 


 

4. @StateObject — 내가 소유하는 참조

 

  • SwiftUI 2.0(iOS 14)에서 도입.
  • View가 해당 객체를 생성하고 생명주기 동안 보존합니다.
struct MyView: View {
    @StateObject private var counter = Counter()
}

 

특징

 

  • 재렌더링해도 인스턴스가 유지됩니다.
  • “내가 만들고 내가 끝까지 관리한다”는 느낌.

 


 

5. @EnvironmentObject — 전역 공유 상태

 

  • 상위 뷰에서 .environmentObject()로 한 번 주입하면, 트리 아래 모든 뷰에서 접근 가능합니다.
struct Root: View {
    @StateObject private var settings = UserSettings()
    var body: some View {
        Child().environmentObject(settings)
    }
}

struct Child: View {
    @EnvironmentObject var settings: UserSettings
}

 

특징

 

  • 앱 전역 설정, 세션 정보 등에 적합.
  • 주입 누락 시 런타임 크래시가 발생합니다.

 


 

6. @Observable (iOS 17+) — 차세대 상태 관리

 

  • @Published 없이 속성 변경을 자동 감지.
  • 자식에서 수정하려면 @Bindable 사용.
@Observable
final class CounterModel {
    var count = 0
}

struct Parent: View {
    @State private var model = CounterModel()
}

struct Child: View {
    @Bindable var model: CounterModel
}

 

특징

 

  • 코드 간결, 성능 개선, Combine 의존성 제거.
  • 최신 iOS 프로젝트라면 우선 고려 대상.

 


 

🛠 오늘 만든 실험 예제

 

저는 위 개념들을 네 가지 데모 화면으로 나눠 구현했습니다.

 

  1. State & Binding — 개인 저장소 vs 연결
  2. ObservedObject vs StateObject — 빌림 vs 소유
  3. EnvironmentObject — 트리 전체 공유
  4. @Observable + @Bindable — 최신 관용구

 

각 화면에서 버튼을 눌러 보고, 콘솔에 init/deinit 로그를 찍어 인스턴스 라이프사이클을 눈으로 확인했습니다.

 


 

예시: ObservedObject vs StateObject 실험

private struct BadObservedParent: View {
    let rerenderTrigger: Bool
    var body: some View {
        ChildObserved(vm: LegacyCounter()) // 매번 새로 생성
    }
}

private struct GoodStateObjectParent: View {
    let rerenderTrigger: Bool
    @StateObject private var vm = LegacyCounter()
    var body: some View {
        ChildObserved(vm: vm) // 한번 만든 인스턴스 재사용
    }
}

 

실행 결과

 

  • ObservedObject 버전: 부모만 재렌더링해도 인스턴스가 새로 만들어져 count가 초기화됨.
  • StateObject 버전: 값이 그대로 유지.

 

이걸 보니 “상태의 소유권”이 얼마나 중요한지 체감됐습니다.

 


 

📊 비교 표

속성                                                            저장 위치                            라이프사이클                                      사용 예

@State View 내부 View와 함께 사라짐 간단한 토글, 입력값
@Binding 원본 참조 원본 상태에 종속 자식이 부모 상태 수정
@ObservedObject 외부 객체 참조 외부 소유자에 따라 변동 주입받아 값 표시
@StateObject View 내부 소유 View 생존 기간 유지 뷰에서 직접 생성·관리
@EnvironmentObject 환경(Context) 환경 주입 기간 유지 전역 설정, 세션
@Observable View 또는 외부 구현 방식에 따라 유지 최신 프로젝트의 상태 전반

 


 

오늘의 인사이트

 

  1. “상태는 소유권이 전부다.”
  2. 누가 만들었고, 누가 책임지는지에 따라 선택지가 달라진다.
  3. Binding은 저장소가 아니다.
  4. 그저 연결선일 뿐, 원본이 없으면 아무것도 못한다.
  5. EnvironmentObject는 양날의 검.
  6. 어디서든 접근 가능하지만, 주입 안 하면 바로 크래시.
  7. iOS 17+에서는 @Observable이 정답에 가깝다.
  8. 코드가 훨씬 깔끔해지고 Combine 의존이 없어진다.

 


 

마무리

 

오늘은 SwiftUI 상태 관리의 전 과정을 실험했습니다.

단순히 문법을 외우는 것과, 직접 해보면서 차이를 눈으로 확인하는 건 완전히 다릅니다.

특히 ObservedObject StateObject의 차이는 콘솔 로그를 찍어보니 머리에 콕 박혔습니다.

 

내일은 C5 플랜 Day 2, 데이터 흐름 기초 설계로 넘어갑니다.
오늘 배운 상태 관리 도구들을 가지고, 데이터가 어떻게 흘러야 하는지 설계할 예정입니다.

728x90
반응형
목차