SwiftUI Study – @StateObject와 @ObservedObject를 정확하게 선택하는 법 (뷰 재생성·초기화 문제 방지)
Dev Study/SwiftUI 2025. 12. 9. 10:05SwiftUI Study – @StateObject와 @ObservedObject를 정확하게 선택하는 법 (뷰 재생성·초기화 문제 방지)
1. 왜 중요한가 (문제 배경)
SwiftUI 개발에서 가장 흔하면서도 치명적인 실수는 다음입니다.
- ViewModel이 계속 재생성된다
- API가 여러 번 호출된다
- @ObservedObject를 써서 ViewModel이 사라지는 문제
- @StateObject를 잘못 사용해 초기화 타이밍이 꼬이는 문제
즉, SwiftUI의 lifecycle을 정확히 이해하지 못하면
의도하지 않은 상태 초기화 / 중복 호출 / 메모리 낭비 / UI 갱신 누락이 발생합니다.
핵심 원칙은 다음과 같습니다.
“해당 ViewModel을 이 View의 생명주기 동안 유지해야 한다면 @StateObject”
“부모에서 만들어진 ViewModel을 전달받아 사용만 한다면 @ObservedObject”사용 목적이 서로 완전히 다르다.
2. 잘못된 패턴 예시
❌ 예시 1: 매번 View가 그려질 때마다 ViewModel 재생성
struct WrongProfileView: View {
@ObservedObject var vm = ProfileViewModel() // ❌ View가 redraw될 때마다 새로 생성됨
var body: some View {
Text(vm.name)
}
}
문제점
- 화면 이동·상태 변화 때마다 ViewModel이 다시 만들어짐
- API가 여러 번 호출됨
- @ObservedObject는 “외부에서 주입된 것”을 의미하므로 내부 생성은 잘못된 패턴
❌ 예시 2: 부모에서 @StateObject를 쓰지 않아 자식 ViewModel도 계속 초기화
struct ParentView: View {
var body: some View {
ChildView(vm: ChildViewModel()) // ❌ 매번 새로운 ViewModel 생성
}
}
문제점
- ChildViewModel이 계속 재생성됨
- 내부 상태가 유지되지 않음
- 특히 NavigationStack 안에서 흔히 보이는 문제
❌ 예시 3: ObservableObject를 여러 View 계층에서 중복 소유
struct WrongListItemView: View {
@ObservedObject var vm: ItemViewModel
var body: some View {
VStack {
Text(vm.title)
}
.onAppear {
vm.load() // ❌ 셀이 재사용될 때마다 여러 번 호출
}
}
}
문제점
- 리스트 스크롤 시 load()가 무한히 호출
- ObservableObject는 재사용될 수 있는 View에 직접 연결하면 위험
3. 올바른 패턴 예시
✅ 예시 1: View 내부에서 생성해서 유지 → @StateObject
struct ProfileView: View {
@StateObject private var vm = ProfileViewModel() // ✅ 한 번만 생성됨
var body: some View {
Text(vm.name)
}
}
장점
- ViewModel이 화면 생명주기 동안 유지됨
- API 중복 호출 없음
- 데이터 손실 없음
✅ 예시 2: 부모에서 생성해 자식이 사용만 할 때 → @ObservedObject
struct ParentView: View {
@StateObject private var vm = ParentViewModel()
var body: some View {
ChildView(vm: vm) // 자식은 상태를 “관찰만” 함
}
}
struct ChildView: View {
@ObservedObject var vm: ParentViewModel // 부모에서 주입받음
var body: some View {
Text(vm.title)
}
}
장점
- ViewModel 소유권을 부모가 가지므로 불필요한 초기화 방지
- 자식은 vm을 관찰만 하므로 안전
✅ 예시 3: NavigationStack / TabView 안에서 ViewModel 소유권 유지
struct RootView: View {
@StateObject private var vm = HomeViewModel() // 여기서 유지해야 함
var body: some View {
NavigationStack {
HomeView(vm: vm)
}
}
}
장점
- 화면 이동 시 ViewModel이 재생성되지 않음
- navigation push/pop 시 상태 유지 가능
4. 실전 적용 팁
✔ 팁 1 – ViewModel을 “생성하는 곳”과 “사용하는 곳”을 명확히 분리
- 생성 → @StateObject
- 사용 → @ObservedObject
✔ 팁 2 – ViewModel을 자식에 전달할 때는 ObservableObject를 무조건 주입
초기화를 자식에서 하지 않도록 한다.
✔ 팁 3 – List/ScrollView 셀 내부에서 ObservableObject를 직접 가지지 말 것
셀 재사용 패턴과 충돌해 load()가 반복된다.
✔ 팁 4 – NavigationStack은 부모 단에서 ViewModel을 소유해야 한다
push/pop마다 ViewModel이 재생성되는 문제 방지.
✔ 팁 5 – @StateObject는 반드시 private으로 보호
외부에서 상태를 덮어쓰는 것을 예방.
5. 정리
- @StateObject는 “이 View가 생명주기를 소유하는 ViewModel”을 의미한다.
- @ObservedObject는 “부모가 관리하는 ViewModel을 관찰하는 것”에 사용한다.
- 잘못 선택하면 ViewModel 초기화가 반복되며 API 중복 호출 같은 문제가 발생한다.
- NavigationStack, 리스트, 탭 구조에서는 올바른 소유권 관리가 필수이다.
- 이 원칙만 제대로 지켜도 SwiftUI 상태 관리 문제의 절반은 해결된다.
'Dev Study > SwiftUI' 카테고리의 다른 글
| SwiftUI Study – PreferenceKey와 AnchorPreference를 안전하게 사용하는 법 (레이아웃 역방향 데이터 전달) (0) | 2025.12.09 |
|---|---|
| SwiftUI Study – View 내부에 비즈니스 로직을 넣지 않고 도메인/UseCase로 분리하는 실전 설계 패턴 (0) | 2025.12.09 |
| SwiftUI Study – List 성능과 안정성을 높이는 ForEach·id 선택 기준 (셀 깜빡임·재배치·중복 렌더링 방지) (0) | 2025.12.09 |
| SwiftUI Study – @EnvironmentObject 안전하게 사용하기 (크래시 방지·전역 상태 오염 방지) (0) | 2025.12.09 |
| SwiftUI Study – NavigationStack을 안정적으로 설계하는 법 (중복 Push·잘못된 경로·상태 꼬임 방지) (0) | 2025.12.09 |
| SwiftUI Study – 이미지 로딩·캐싱·리사이징을 효율적으로 처리하는 실전 패턴 (0) | 2025.12.09 |
| SwiftUI Study – ScrollView 안에서 LazyVStack을 사용해 성능을 최적화하는 방법 (0) | 2025.12.09 |
| SwiftUI Study – body 안에서 무거운 연산을 실행하지 않는 이유와 해결법 (0) | 2025.12.09 |

