StateObject와 ObservedObject 차이

SteadySlower·2023년 6월 29일
0

SwiftUI

목록 보기
51/64

저번 포스팅에 SwiftUI에서 View의 re-init이라는 주제로 @State 변수의 특성에 대해서 다뤘는데요. 이번 포스팅은 그 포스팅을 배경지식으로 둔 포스팅입니다. 당시 포스팅에서 결론을 @State 변수는 별도로 관리되고 따라서 상위 View의 State 변화에 영향을 받지 않습니다. 유식한(?) 말로 바꾸면 View와 별도의 라이프 사이클을 가진다는 뜻입니다.

StateObject와 ObservedObject도 같은 맥락입니다. 둘은 거의 유사하지만 라이프 사이클 차원에서는 차이가 있습니다.

ObservedObject

ObservedObject의 가장 큰 특징은 View와 라이프 사이클을 함께 한다는 점입니다.

아래 코드를 보시죠. 부모 View안에 ChildView가 있습니다. 부모 View는 토글을 하나 가지고 있고요. 자식 View는 ObservedObject로 선언된 ViewModel을 가지고 있습니다. ChildView에 있는 버튼을 누르면 ObservedObject에 있는 Int 값이 1씩 증가합니다.

struct ParentView: View {
    
    @State var bool: Bool = false
    
    var body: some View {
        VStack {
            Toggle(isOn: $bool) {
                Text("토글")
            }
            ChildView()
        }
        .padding(.horizontal, 50)
    }
}

struct ChildView : View {
    
    @ObservedObject var vm = ChildViewModel()
    
    init() {
        print("Child View inited")
    }
    
    var body: some View {
        HStack {
            Text("\(vm.count)")
            Spacer()
            Button("+") {
                vm.count += 1
            }
        }
    }
    
}

class ChildViewModel: ObservableObject {
    @Published var count: Int
    
    init() {
        self.count = 0
        print("Child ViewModel inited")
    }
}

Int 값을 증가시키다고 토글을 누르면 아래와 같이 작동하는 것을 볼 수 있습니다. 토글을 눌러서 ChildView가 reinit 되었을 때 ObservedObject도 함께 reinit됩니다. (print문으로 확인할 수 있습니다.) 즉 라이프 사이클을 공유합니다.

@StateObject

반면에 같은 코드를 StateObject로만 변경을 해봅시다. 아래 코드는 완전히 동일한 코드에 @ObservedObject를 @StateObject로만 변경한 코드입니다.

struct ParentView: View {
    
    @State var bool: Bool = false
    
    var body: some View {
        VStack {
            Toggle(isOn: $bool) {
                Text("토글")
            }
            ChildView()
        }
        .padding(.horizontal, 50)
    }
}

struct ChildView : View {
    
    @StateObject var vm = ChildViewModel()
    
    init() {
        print("Child View inited")
    }
    
    var body: some View {
        HStack {
            Text("\(vm.count)")
            Spacer()
            Button("+") {
                vm.count += 1
            }
        }
    }
    
}

class ChildViewModel: ObservableObject {
    @Published var count: Int
    
    init() {
        self.count = 0
        print("Child ViewModel inited")
    }
}

같은 동작을 해보겠습니다. 결과는 아래와 같습니다. 이번에는 toggle을 눌러서 ChildView를 reinit해도 StateObject는 reinit 되지 않습니다. (print문으로 확인할 수 있습니다.) 즉 별도의 라이프 사이클을 가지고 있음을 알 수 있습니다.

static.com/4ed72829-379b-4d21-8e40-7214ef69f49a/state.gif)

정리

StateObject는 State와 동일하다

StateObject는 State 변수 처럼 View와 별도의 라이프 사이클을 가집니다. 따라서 상위 View에서 해당 View를 reinit해도 영향을 받지 않습니다. 또한 완전한 독립성을 보장하기 위해서 setter가 없기 때문에 initilizer에서 주입을 하려고 해도 아래와 같은 에러가 보여집니다. (반면에 ObservedObject는 외부에서 주입을 할 수 있습니다.)

언제 무엇을 쓸 것인가?

대부분의 ViewModel의 경우 StateObject만 써도 무방할 것 같습니다. 데이터를 담당하는 객체가 View와 라이프 사이클을 공유하면 곤란한 경우가 많으니까요. SwiftUI가 언제 View를 reinit 하는지에 대해서 개발자가 다 알 수도 없구요.

하지만 StateObject가 만능은 아닙니다. StateObejct는 init으로 받아올 수 없습니다. 따라서 다른 View 들과 데이터를 공유하는 객체가 필요할 때는 StateObject를 사용할 수 없습니다. 다른 곳에 이미 ObservableObject의 인스턴스가 존재하고 그 인스턴스의 참조를 받아서 사용하는 경우는 ObservedObject를 사용하면 됩니다.

profile
백과사전 보다 항해일지(혹은 표류일지)를 지향합니다.

0개의 댓글