Swift 상태관리

BS_Lee·2025년 7월 20일

swift

목록 보기
18/21

SwiftUI 상태 관리 완전 정리

@State, @Binding, @StateObject, @ObservedObject, @EnvironmentObject

SwiftUI로 앱을 만들다 보면 “상태 관리”가 정말 중요하다는 걸 느끼게 된다.
값이 바뀔 때마다 UI가 자동으로 업데이트되니 편리하지만, 상태의 소유권과 생명주기를 제대로 이해하지 않으면 예상치 못한 버그를 맞이할 수 있다.

그럼 하나씩 정리해보자.


1. @State — 로컬 상태

정의

@State뷰 내부에서만 관리되는 로컬 상태다.
값 타입(Int, String, struct)에 적합하며, 값이 바뀌면 뷰가 자동으로 다시 그려진다.

의문

“이걸 어디에 써야 하지?”
뷰 내부에서만 쓰는 값일 때 사용하면 된다. 외부와 공유할 필요가 없을 때 가장 적합하다.

예시 코드

struct CounterView: View {
    @State private var count = 0  // 로컬 상태

    var body: some View {
        VStack {
            Text("Count: \(count)")
            Button("증가") {
                count += 1  // 값 변경 → UI 자동 업데이트
            }
        }
    }
}

2. @Binding — 상위 상태를 하위에서 수정

정의

@Binding상위 뷰의 @State 값을 하위 뷰에서 읽고 수정할 수 있게 연결해 준다.

의문

“하위 뷰가 상태를 직접 소유하면 안 되나?”
→ 하위에서 소유하면 상위와 값이 따로 놀게 된다. 상위 상태를 공유해야 한다면 Binding이 정답이다.

예시 코드

struct ParentView: View {
    @State private var name = "홍길동"

    var body: some View {
        VStack {
            Text("이름: \(name)")
            ChildView(name: $name)  // Binding으로 전달
        }
    }
}

struct ChildView: View {
    @Binding var name: String  // 상위의 상태에 직접 연결

    var body: some View {
        TextField("이름 입력", text: $name)
            .textFieldStyle(.roundedBorder)
    }
}

하위에서 이름을 바꾸면 부모 뷰의 name도 즉시 바뀐다.


3. @StateObject — 참조 타입 상태의 소유자

정의

@StateObject뷰가 직접 ObservableObject를 소유하고 관리할 때 사용한다.
SwiftUI가 객체의 생명주기를 관리하며, 값이 바뀌면 뷰가 다시 그려진다.

의문

“왜 그냥 @State로 클래스 타입을 쓰면 안 돼?”
→ @State는 값 타입 전용이다. 클래스 같은 참조 타입은 @StateObject를 써야 한다.

예시 코드

class UserSettings: ObservableObject {
    @Published var score = 0
}

struct StateObjectExample: View {
    @StateObject private var settings = UserSettings()  // 뷰가 소유

    var body: some View {
        VStack {
            Text("점수: \(settings.score)")
            Button("점수 증가") {
                settings.score += 1
            }
        }
    }
}

@StateObject는 뷰가 새로 그려져도 기존 객체를 유지한다.


4. @ObservedObject — 다른 뷰에서 만든 객체 관찰

정의

@ObservedObject상위에서 이미 만든 ObservableObject를 관찰만 할 때 사용한다.
뷰는 이 객체를 소유하지 않는다.

의문

“그럼 @StateObject랑 뭐가 다른데?”
소유권의 차이다.

  • @StateObject: 내가 소유 → 내가 생성
  • @ObservedObject: 남이 소유 → 나는 관찰만

예시 코드

struct ParentView: View {
    @StateObject private var settings = UserSettings()  // 부모가 소유

    var body: some View {
        VStack {
            Text("부모 점수: \(settings.score)")
            ChildView(settings: settings)
        }
    }
}

struct ChildView: View {
    @ObservedObject var settings: UserSettings  // 단순 관찰

    var body: some View {
        VStack {
            Text("자식 점수: \(settings.score)")
            Button("점수 증가") {
                settings.score += 1
            }
        }
    }
}

부모와 자식이 같은 객체를 공유하며, 값이 바뀌면 둘 다 업데이트된다.


5. @EnvironmentObject — 전역 상태 공유

정의

@EnvironmentObject는 앱 전역에서 공유하는 상태를 주입받을 때 사용한다.
상위에서 .environmentObject(_:)로 한 번만 넣어주면 하위 뷰 어디서든 접근 가능하다.

의문

“그냥 파라미터로 넘기면 되지 않나?”
→ 뷰 계층이 깊어질수록 파라미터로 계속 넘기는 게 귀찮아진다.
@EnvironmentObject를 쓰면 이 문제를 깔끔하게 해결할 수 있다.

예시 코드

class AppSettings: ObservableObject {
    @Published var themeColor: Color = .blue
}

@main
struct MyApp: App {
    @StateObject private var settings = AppSettings()

    var body: some Scene {
        WindowGroup {
            RootView()
                .environmentObject(settings)  // 전역 주입
        }
    }
}

struct RootView: View {
    var body: some View {
        ChildView()  // 별도 파라미터 필요 없음
    }
}

struct ChildView: View {
    @EnvironmentObject var settings: AppSettings

    var body: some View {
        VStack {
            Text("테마 색상")
                .foregroundColor(settings.themeColor)
            Button("색상 변경") {
                settings.themeColor = .red
            }
        }
    }
}

단, 주입하지 않으면 런타임 에러가 난다.


한눈에 비교

래퍼소유권대상사용 위치용도
@State뷰가 소유값 타입로컬뷰 내부 상태
@Binding상위가 소유값 타입하위상위 상태 수정
@StateObject뷰가 소유참조 타입초기 생성뷰가 직접 관리
@ObservedObject상위가 소유참조 타입하위상위 객체 관찰
@EnvironmentObject전역참조 타입전역 하위 뷰전역 상태 공유

결론 — 한 줄 요약

  • 소유권이 핵심이다.
  • 내가 만든 값 → @State / @StateObject
  • 남이 준 값 → @Binding / @ObservedObject
  • 전역 공유 → @EnvironmentObject

0개의 댓글