@StateObject는 SwiftUI의 property wrapper로, View의 전체 수명 주기 동안 ObservableObject 인스턴스를 생성하고 소유합니다. View가 처음 화면에 나타날 때 @StateObject는 객체를 초기화하고 View가 메모리에서 제거될 때까지 저장합니다. 이는 인터페이스 재구축 시(예: 테마 변경 또는 부모 View 업데이트) 데이터가 재설정되지 않도록 보장합니다. Apple Developer Documentation(2025)에 따르면, @StateObject는 SwiftUI 계층 구조에서 ObservableObject의 기본 정보 소스(source of truth)로 사용되어야 하며, 자식 View는 @ObservedObject 또는 @EnvironmentObject를 통해 이미 생성된 객체를 받습니다.
주요 포인트
@StateObject는 iOS 14에서 도입된 property wrapper로, View가 ObservableObject 프로토콜을 준수하는 클래스의 인스턴스를 생성하고 소유할 수 있게 합니다. 값 유형(struct)으로 작동하는 @State와 달리, @StateObject는 참조 유형 — 속성 변경 사항을 SwiftUI에 알릴 수 있는 클래스 — 을 위해 설계되었습니다.
View가 @StateObject var viewModel: MyViewModel을 사용하면 SwiftUI는 View가 처음 표시될 때 MyViewModel의 인스턴스를 자동으로 생성하고 프레임워크의 특별한 저장소에 보관합니다. View가 업데이트될 때마다(예: 부모 상태가 변경될 때) SwiftUI는 객체를 다시 생성하지 않고 View가 계층 구조에서 제거될 때까지 기존 인스턴스를 사용합니다.
Apple WWDC Session 10137(2024)에 따르면, @StateObject는 iOS 13에서 View가 재구축될 때 존재했던 데이터 손실 문제를 해결합니다. 이전에는 개발자가 부모 View에서 ObservableObject를 생성하고 초기화자를 통해 전달해야 했으며, 이로 인해 코드 중복과 실수로 객체가 다시 생성될 위험이 있었습니다.
import SwiftUI
class CounterViewModel: ObservableObject {
@Published var count: Int = 0
func increment() {
count += 1
}
}
struct CounterView: View {
@StateObject var viewModel = CounterViewModel()
var body: some View {
VStack {
Text("Count: \(viewModel.count)")
Button("Increment", action: viewModel.increment)
}
}
}
@StateObject 메커니즘은 SwiftUI와 Combine 프레임워크의 통합에 기반합니다. ObservableObject가 속성을 @Published 속성으로 표시하면 SwiftUI는 ObservableObject 프로토콜에 내장된 퍼블리셔를 통해 변경 사항을 자동으로 구독합니다. 게시된 속성이 변경되면 객체는 objectWillChange 퍼블리셔를 통해 신호를 보내고, 이 객체를 관찰하는 모든 View의 다시 그리기를 트리거합니다.
SwiftUI는 ObservableObject 인스턴스를 특정 View 인스턴스에 연결된 특별한 저장소에 보관합니다. 이 저장소는 첫 번째 렌더링 시 한 번 생성되며 View가 소멸될 때까지 존재합니다. 이것이 @StateObject가 참조 안정성을 보장하는 이유입니다 — SwiftUI는 View의 초기화자에 의존하지 않고 메모리를 자동으로 관리합니다.
objc.io — Thinking in SwiftUI(2025)에 따르면, @StateObject의 내부 구현은 @State와 유사한 메커니즘을 사용하지만 참조 유형용입니다. SwiftUI는 객체 주위에 박싱 래퍼를 만들고 빈번한 View 계층 구조 재구축에 최적화된 자체 할당자를 통해 수명 주기를 관리합니다.
@StateObject와 @ObservedObject의 주요 차이점은 누가 객체를 소유하는지에 있습니다. @StateObject는 객체를 생성하고 저장합니다 — 그것이 소유자입니다. @ObservedObject는 다른 곳에서 생성되어 초기화자나 속성을 통해 전달된 객체를 관찰만 합니다.
| 특성 | @StateObject | @ObservedObject |
|---|---|---|
| 소유권 | 객체를 생성하고 소유함 | 관찰만 함 |
| 초기화 | View 내에서 init/기본값을 통해 | 외부에서 매개변수를 통해 전달됨 |
| 수명 주기 | View의 수명 주기에 연결됨 | View에 의해 제어되지 않음 |
| 재생성 | 업데이트 시 다시 생성되지 않음 | 외부에서 교체될 수 있음 |
| iOS 버전 | iOS 14+ | iOS 13+ |
규칙은 간단합니다. View가 ObservableObject를 생성하는 경우 @StateObject를 사용합니다. View가 부모로부터 이미 생성된 객체를 받기만 하는 경우 @ObservedObject를 사용합니다. 이 규칙을 위반하면 데이터 손실(소유권에 @ObservedObject 사용) 또는 과도한 객체 생성(관찰에 @StateObject 사용)이 발생합니다.
@StateObject는 특정 데이터 집합의 정보 소스 역할을 하는 View에서 사용해야 합니다. 일반적인 시나리오에는 자체 view model이 있는 화면, 네비게이션 스택의 루트 화면 및 자체 상태를 관리하는 모달 프레젠테이션이 포함됩니다.
struct ProfileView: View {
@StateObject var viewModel = ProfileViewModel()
var body: some View {
NavigationStack {
Form {
TextField("Name", text: $viewModel.name)
TextField("Email", text: $viewModel.email)
Button("Save") {
viewModel.saveProfile()
}
}
.navigationTitle("Profile")
}
}
}
@StateObject를 매개변수로 초기화하려면 특별한 구문이 필요합니다. SwiftUI가 객체 생성을 자체적으로 관리하기 때문입니다. 초기화자에 단순히 매개변수를 전달할 수 없습니다 — 이스케이핑 클로저나 별도의 팩토리 메서드를 사용해야 합니다.
Swift by Sundell(2024)에 따르면, 가장 깔끔한 방법은 SwiftUI가 객체를 처음 생성할 때 호출할 팩토리 메서드 또는 클로저를 사용하는 것입니다. 대안적인 접근 방식은 부모 View에서 ObservableObject를 초기화하고 표준 초기화자를 사용하여 @StateObject를 통해 전달하는 것입니다.
class UserViewModel: ObservableObject {
@Published var user: User
init(user: User) {
self.user = user
}
}
struct UserDetailView: View {
@StateObject var viewModel: UserViewModel
init(user: User) {
_viewModel = StateObject(wrappedValue: UserViewModel(user: user))
}
var body: some View {
Text(viewModel.user.name)
}
}
@StateObject가 있는 View 초기화자는 property wrapper 자체에 접근하기 위해 속성 이름 앞에 밑줄(_viewModel)을 사용해야 합니다. 이는 초기화자에서 property wrapper로 작업하기 위한 표준 Swift 패턴입니다.
가장 흔한 실수는 객체를 소유해야 하는 View에서 @StateObject 대신 @ObservedObject를 사용하는 것입니다. 이 경우 부모가 재구축될 때마다 객체가 다시 생성되어 축적된 모든 데이터가 손실됩니다. 이 실수는 NavigationStack 또는 TabView가 있는 복잡한 계층 구조에서 특히 교묘합니다.
이러한 문제를 피하려면 간단한 규칙을 따르세요. 정보 소스당 하나의 @StateObject. 데이터를 여러 화면에서 공유해야 하는 경우 루트 View에서 @StateObject를 한 번 생성하고 @ObservedObject 또는 @EnvironmentObject를 통해 자식 요소에 전달합니다.
// ❌ Wrong: @ObservedObject for owning an object
struct BadView: View {
@ObservedObject var vm = ViewModel() // will be recreated on each update!
}
// ✅ Correct: @StateObject for owning
struct GoodView: View {
@StateObject var vm = ViewModel() // created once for View lifetime
}
자주 묻는 질문
@State는 값 유형(struct, 문자열, 숫자)으로 작동하며 값을 SwiftUI 저장소에 직접 저장합니다. @StateObject는 참조 유형 — ObservableObject를 준수하는 클래스 — 으로 작동합니다. @State는 간단한 로컬 상태에 적합하며, @StateObject는 로직과 게시된 속성이 있는 복잡한 객체에 적합합니다.
아니요, @StateObject는 iOS 14 이상에서만 사용할 수 있습니다. iOS 13의 경우 @ObservedObject를 사용하고 수동 수명 주기 관리와 함께 @State를 통해 부모 View에서 ObservableObject를 생성하세요. 대안으로 참조 의미 체계가 필요하지 않은 데이터에는 class 대신 struct와 함께 @State를 사용할 수 있습니다.
자식 View는 부모와 완전히 독립적인 ObservableObject의 자체 복사본을 생성합니다. 한쪽의 변경 사항은 다른 쪽에 영향을 미치지 않습니다. 이는 거의 항상 오류입니다. 부모로부터 객체를 받으려면 @ObservedObject를 사용하고 View 내에서 새 객체를 생성할 때만 @StateObject를 사용하세요.
객체를 생성한 View가 SwiftUI 계층 구조에서 완전히 제거될 때 객체가 소멸됩니다. NavigationStack의 화면의 경우 네비게이션 스택에서 팝될 때 발생합니다. 모달 창의 경우 닫힐 때입니다. TabView의 경우 View가 캐시되지 않은 경우 탭을 전환할 때입니다.
밑줄을 통해 property wrapper에 접근하는 사용자 정의 init을 사용하세요: _viewModel = StateObject(wrappedValue: MyViewModel(param: value)). 이 패턴을 사용하면 View 수명 동안 단일 객체 생성이 보장되면서 ObservableObject에 모든 매개변수를 전달할 수 있습니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.