seong_hye, the developer

Swift) StateObject, ObservableObject, EnvironmentObject 본문

IOS

Swift) StateObject, ObservableObject, EnvironmentObject

seong_hye 2022. 11. 2.

 

모두 데이터와 뷰 상태를 연결하기 위한 속성 래퍼이다

이들은 각각의 역할과 적용 범위, 생명주기가 다르기 때문에 구분해서 사용하는 것이 중요하다


📘 ObservableObject

SwiftUI에서 클래스 기반 상태를 뷰와 연결해주는 핵심 프로토콜

뷰가 해당 객체를 구독하고 내부 값이 바뀌면 뷰가 자동으로 업데이트됨

주로 크래스를 기반으로하는 뷰모델을 만들 때 사용함

 

🔹예시

// 상태 객체 선언
class CounterModel: OBsrvableObject {
	@Published var count = 0
}

// 뷰에서 연결
struct TestView: View {
	@ObservedObject var viewModel: CounterModel
	
    var body: some View {
    	VStack {
        	Button("값 추가") {
            	viewModel.count += 1
            }
		}
    }
}

 

✅@Published

@Published는 @ObservableObject의 필수 파트

@Published가 붙은 프로터티가 바뀌면, 뷰에 변경 신호가 전달됨

값이 바뀔 때 Combine의 Publisher를 통해 objectWillChange를 자동으로 호출함

 

🔹특징

ObservableObject는 클래스에서만 사용 가능 (구조체 X)

@Published를 빼먹으면 뷰가 갱신되지 않음

뷰가 제대로 연결되지 않으면 생명주기나 상태가 꼬일 수 있음

 

🔹사용 경우

로그인 상태 관리 여러 뷰에 로그인 상태 전파
API 통신 결과 저장 비동기 결과를 반영해 뷰 업데이트
사용자 설정 관리 다수 뷰에서 공통 데이터 접근

📘 StateObject

클래스 기반의 상태 객체(ObservableObject)를 뷰에서 직접 생성하고 소유할 때 사용하는 속성 래퍼

뷰가 직접 상태를 소유하고 유지함

뷰의 생명주기와 함께 객체의 생명주기도 관리됨

~> 다시 생성되더라도 @StateObject는 한 번만 생성됨 

 

🔹사용하는 이유?

@ObservedObject만 쓰던 초기에 문제가 있었음

- 뷰가 재생성될 때마다 @ObservedObejct가 새로 생성됨 -> 상태 초기화 문제 발생

- 이를 방지하기 위해 소유하고 있는 상태 객체에는 @StateObejct를 써야한다는 개념이 생김 (iOS 14부터 도입)

 

🔹예시

// 상태 객체 선언
class CounterModel: OBsrvableObject {
	@Published var count = 0
}

// 뷰에서 연결
struct TestView: View {
	@StateObject var viewModel = CounterModel()
	
    var body: some View {
    	VStack {
        	Button("값 추가") {
            	viewModel.count += 1
            }
		}
    }
]

 

✅@StateObject를 사용하는 경우

상황 사용해야 하는가?
뷰 안에서 ObservableObject를 직접 만들 때 O (@StateObject)
부모 뷰에서 넘겨받은 상태를 관찰할 때 X (@ObservedObject)
여러 뷰에서 공유해야 할 때 (전역 상태) X (@EnvironmentObject)

 

✅@ObservedObject vs @StateObject

속성 객체 생성 생명주기 관리 사용 위치
@StateObject 해당 뷰에서 생성 SwiftUI가 관리 "처음"상태 객체 생성 시
@ObservedObject 외부에서 주입됨 외부에서 관리 자식 뷰에서 상태 관찰만 할 때

 

✅생명주기 예시

//잘못된 예시
struct WrongView: View {
	@ObservedObject var modal = CounterModel() // 잘못된 사용 (매번 초기화됨)
}

//올바른 예시
struct CorrectView: View {
	@StateObject var modal = CounterModel() // 한 번만 초기화
}

 

🔹요약

항목 설명
의미 ObservableObject를 뷰가 소유할 때 사용하는 속성
역할 한 번만 초기화되고 뷰 재생성 시에도 유지
도입 시기 iOS 14부터
보통 사용 위치 상태 객체를 직접 생성하는 최상위 뷰
내부에서 사용되는 기술 Combine의 objectWillChange 구독

📘 EnvironmentObject

상위에서 주입한 전역 상태 객체를 하위 뷰에서 쉽게 공유할 수 있도록 도와주는 속성 래퍼

SwiftUI 환경에 저장하고 공유하는 방법

상위 뷰에서 .envrionmentObject(_:)로 전달된 객체를 하위 뷰에서 @EnvironmentObject로 꺼내서 사용

큰 앱일수록 복잡한 데이터 전달 없이 전역 상태를 깔끔하게 관리할 수 있음

 

🔹기본 상태 흐름

//상태 객체 정의
class UserSettings: ObservableObject {
	@Published var username: String = "홍길동"
}

// 최상위 뷰에서 .environmentObejct()로 주입
@main
struct TestApp: App {
	var settings = UserSettings()
    
    var body: some Scene {
    	WindowGroup {
        	ContentView()
            	.environment(settings) // 전역 상태 주입
        }
    }
}

// 하위 뷰에서 @EnvironmentObject로 사용
struct ContentView: View {
	@Environment var settings: UseSettings // 공유 상태 접근
    
    var body: some View {
    	VStack {
        	Text("사용자 이름: \(settings.username)")
            Button("이름 변경") {
            	settings.username = "이순신
            }
        }
    }
}

 

📌 상위 뷰에서 .environmentOjbect()로 주입하지 않으면 런타임 오류 발생

⚠️Fatal error: No ObservableObject of type UserSettings found in environment

 

🔹사용하는 경우

- 앱 전체에서 공통 데이터를 공유하고 싶을 때

- 부모 -> 자식 -> 손자 뷰로 데이터 전달하는 게 귀찮을 때

- 의존성 주입처럼 객체를 context에서 꺼내 쓰고 싶을 때

 

🔹장점과 단점

장점 단점
상위에서 한 번만 주입하면 하위 전체에서 사용 가능 주입 누락 시 런타임 오류 발생
코드가 간결해짐(prop drilling 제거) 의존성이 명시적이지 않음 (숨겨짐)
앱 전체 전역 상태 공유에 적합 너무 많이 사용하면 구조가 애매해질 수 있음

 

🔹요약

항목 설명
타입 ObservableObject의 전역 공유
사용 위치 주로 상위 앱/루트 뷰
연결 방식 .environmentObject(_:) -> @EnvironmentObject
대표 예시 로그인 상태, 사용자 정보, 테마, 설정 등

 


🔹핵심 차이 비교

속성 사용 위치 객체 생성 위치 사용 목적  소유자
@StateObject 뷰 내부 뷰 내부에서 생성 상태를 직접 소유 해당 뷰
@ObservedObject 뷰 내부 외부에서 전달 상태를 구독만 부모 뷰 등
@EnvironmentObject 전역 공유 상위 환경에서 주입 전역 상태 공유 전역 (App, Scene 등)

 

🔹사용하는 경우 차이

상태 객체를 처음 생성해야 함 -> @StateObject

이미 만들어진 객체를 자식 뷰에 넘김 -> @ObservedObject

상위 뷰에서 전체 앱에 공유하고 싶음 -> @EnvironmentObject


 

Comments