StateObject는 이전 포스팅에서 다룬 ObservedObject처럼 관찰 가능한 객체를 생성해주는 속성 래퍼 유형입니다.
이 두 래퍼는 비슷해 보이지만 중요한 차이점이 존재합니다.
이 글에서는 StateObject의 개념을 정리하고 StateObject와 ObservedObject의 차이점과 함께, StateObject를 언제 사용해야 하는지에 대해 자세히 알아보겠습니다.
정의를 한번 보도록 합시다.
@MainActor @frozen @propertyWrapper @preconcurrency
struct StateObject<ObjectType> where ObjectType : ObservableObject
정의 역시 ObservedObject와 유사합니다.
이처럼 둘은 비슷하기에 StateObject를 사용한 곳에 ObservedObject를 사용하여도 큰 차이점이 느껴지지 않을 수도 있습니다.
하지만 이 둘은 명백한 차이점이 존재합니다. 이에 대해 더 자세히 알아보도록 하겠습니다.
StateObject를 통해서 관찰되고 있는 객체는 그들을 가지고 있는 화면 구조가 재생성 되어도 파괴되지 않습니다.
import SwiftUI
//ObservableObject은 클래스 전용 프로토콜
class CounterViewModel: ObservableObject {
// 값이 변경되는 것을 즉각적으로 view에 알려줌
@Published var count: Int = 0
// count를 +1씩 올려주는 메소드
func incrementCounter() {
count += 1
}
}
struct CounterView: View {
//외부 클래스를 가져옴 count 변수 활용 할 수 있음
@ObservedObject var counterViewModel: CounterViewModel = CounterViewModel()
var body: some View {
VStack {
Text("\(counterViewModel.count)")
Button {
counterViewModel.incrementCounter()
} label: {
Text("1씩 증가")
}
}
}
}
struct ContentView: View {
@State private var randomInt: Int = 0
var body: some View {
VStack {
Text("\(randomInt)")
Button {
randomInt = (0..<1000).randomElement()!
} label: {
Text("random Number")
}
CounterView()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
위 예제 코드를 실행해 보면 random 버튼을 누를 때마다 숫자가 0으로 초기화 되는 것을 확인할 수 있습니다.
ObservedObject는 관찰하는 객체가 변화면 화면을 다시 그리기 때문에 randomeInt의 값이 변하게 되면 뷰가 다시 렌더링 될 것이고 CounterView 역시 다시 초기화 되어 렌더링 됩니다.
그럼 ObservedObject 대신 StateObject를 사용해 보겠습니다.
struct CounterView: View {
//@ObservedObject --> StateObject 변경
@StateObject var counterViewModel: CounterViewModel = CounterViewModel()
var body: some View {
VStack {
Text("\(counterViewModel.count)")
Button {
counterViewModel.incrementCounter()
} label: {
Text("1씩 증가")
}
}
}
}
이제 다시 실행해 보면 숫자가 0으로 초기화 되지 않는 것을 확인할 수 있습니다.
이런 일이 가능한 이유는 StateObject가 객체를 생성하고 관리하기 때문입니다.
StateObject는 뷰가 처음 초기화 될 때 객체를 생성하고, 이후 뷰가 다시 렌더링 되어도 새로운 객체를 생성하지 않고 기존 객체를 사용합니다.
이렇게 기존 객체를 사용할 수 있는 이유는 선언된 객체가 뷰에 의해 참조되지만, 뷰와 별도의 메모리 공간에 저장하기 때문입니다.
따라서 CounterViewModel을 ContentView와 별도의 메모리에 저장하기 때문에 데이터를 안전하게 보관할 수 있는 것입니다.
반면에, ObservedObject 역시 뷰와 별도의 메모리에 저장되긴 하지만, 객체를 새로 생성하지 않고 외부에서 주입된 객체를 관찰하는 역할만 합니다.
이러한 차이로 인해 ObservedObject를 사용할 경우, 뷰의 상태가 유지되지 않게 됩니다.
차이점에 대해서는 알아봤으니 이번에는 StateObject를 정확히 언제 사용해야 하는지에 대해 알아보도록 하겠습니다.
SwiftUI가 뷰를 다시 그릴 수 있는 가능성이 있다면, 이 경우에는 ObservedObject를 사용하는 것이 바람직하지 않습니다.
위에서도 잠시 설명한대로 ObservedObject는 객체를 직접 생성하지 않고 외부에서 생성된 객체를 주입받아 관찰합니다. 그렇기 때문에 객체를 직접 생성할 필요가 있는 경우에 ObservedObject를 사용하면 안 됩니다. 이 경우 뷰가 다시 렌더링 될 경우 기존 객체가 유지되지 못할 수 있기 때문입니다.
따라서 객체를 직접 생성해야 하는 경우에는 StateObject를 사용해야 화면이 다시 그려져도 항상 같은 결과를 얻을 수 있습니다.
글을 읽다 보면 모든 곳에 ObservedObject 대신 StateObject를 사용하면 머리도 덜 아프고 더 좋지 않나 생각할 수 있습니다.
하지만 동일한 StateObject 인스턴스를 관찰하고 있는 자식들은 객체를 프로퍼티 래퍼로 표시하게 되면, 여러군데에서 객체의 라이프사이클을 관리하게 되기 때문에 비효율적입니다.
따라서 객체를 외부에서 주입하는 경우라면 ObservedObject를 사용하는 것이 더 바람직합니다.
객체를 외부에서 주입받아 관찰하기 때문에 객체의 생성과 생명 주기를 관리할 필요가 없기 때문입니다.
StateObject
ObservedObject
오늘은 StateObject에 대해 알아보고 ObservedObject와는 어떤 점이 다른지 비교해 봤습니다.
StateObject의 개념과 차이점에 대해서 대략적으로는 알고 있었는데 이렇게 자세하게 글을 작성하니 개념들이 더 확실하게 정리되는 것 같아요.
이 글을 보는 분들도 StateObject의 개념과 ObservedObject와의 차이점을 명확히 알았으면 좋겠습니다.🙂
출처
https://developer.apple.com/documentation/swiftui/stateobject
https://green1229.tistory.com/229
https://yeomir.tistory.com/16
https://pilgwon.github.io/post/state-object-vs-observed-object