iOS

[SwiftUI] @StateObject와 @ObservedObject는 뭐가 다를까?

GODOLs 2023. 11. 5. 12:10

지난번에는 EnvironmentObject에 대해서 알아봤지만 사실 더 먼저 알아야하는 것이 이 두 Wrapper인 @StateObject와 @ObservedObject입니다.

@StateObject와 @ObservedObject: 차이점 설명

 

 

@StateObject@ObservedObject 속성 래퍼는 Observed 객체의 변경에 대한 응답으로 SwiftUI 뷰를 업데이트하도록 지시합니다. 두 속성 래퍼 계층으로 유사해 보이지만 SwiftUI에서 앱을 구축할 때 알아야 할 중요한 차이점이 있습니다.

처음에는 왜 항상 @ObservedObject를 잘 사용을 안했는지 이유를 몰랐습니다. (그냥 써야한다고해서 그냥 썼던 기억밖에.. ㅎㅎ)


그러다 두개의 차이에 대한 의문과, 공식문서를 연구하면서 SwiftUI에서 @StateObject의 목적을 알게 되면서 일부 경우에는 @StateObject가 얼마나 필요한지 깨닫게 되었습니다.

@ObservedObject란?

@StateObject@ObservedObject의 차이점을 알아보기 전에 @ObservedObject가 무엇인지 이해하는 것이 좋습니다. 두 속성 래퍼 모두 객체가 ObservableObject 프로토콜을 준수하도록 요구합니다. 이 프로토콜은 객체가 변경되기 전에 발행자(@Published 변수)가 있음을 나타내며, SwiftUI에게 뷰 다시 그리기를 트리거하도록 알려줍니다.

 

예를 들면 다음과 같은 카운터 뷰가 있습니다:

final class CounterViewModel: ObservableObject {
    @Published var count = 0

    func incrementCounter() {
        count += 1
    }
}

struct CounterView: View {
    @ObservedObject var viewModel = CounterViewModel()

    var body: some View {
        VStack {
            Text("Count is: \(viewModel.count)")
            Button("Increment Counter") {
                viewModel.incrementCounter()
            }
        }
    }
}

@StateObject란?

@StateObject 속성 래퍼는 @ObservedObject와 유사하게 작동합니다.

struct CounterView: View {
    @StateObject var viewModel = CounterViewModel()

    var body: some View {
        VStack {
            Text("Count is: \(viewModel.count)")
            Button("Increment Counter") {
                viewModel.incrementCounter()
            }
        }
    }
}

이러한 차이점을 이해하는 것은 다른 뷰가 현재 뷰를 포함하고 있는 경우 중요합니다.

그래서 차이가 뭔데요??

이 작동 방식을 보여주기 위해 이전에 사용했던 카운터 뷰를 다른 뷰 안에 포함시켜 보겠습니다:

struct RandomNumberView: View {
    @State var randomNumber = 0

    var body: some View {
        VStack {
            Text("랜덤 숫자는: \(randomNumber)")
            Button("숫자 무작위로") {
                randomNumber = (0..<1000).randomElement()!
            }
        }.padding(.bottom)
        //카운터뷰 안에 @ObservedObject가 들어가 있습니다.
        CounterView() 
    }
}

 

랜덤 숫자 뷰를 사용하면 '숫자 무작위로' 버튼을 눌러 랜덤 숫자를 생성할 수 있습니다. @State 속성 래퍼에 의해 표시된 randomNumber 속성은 뷰를 다시 그리게 되어 자식뷰인 CounterView도 다시 생성됩니다.

 

 

@ObservedObject를 사용하니 @State가 바뀔때 마다 @Published 변수가 초기화 됩니다.

 

새로운 랜덤 숫자가 생성될 때 CounterView 내에서 @ObservedObject를 사용하면 @Published의 변수인 카운터가 초기화되는 것을 볼 수 있습니다.

 

이 문제를 해결하는 방법은 CounterView의 뷰 모델 속성을 @ObservedObject 대신 @StateObject로 변경하는 것만큼 간단합니다. 위에서 설명했듯이, state 객체는 뷰 모델이 뷰 다시 그리기 사이에 유지되도록 보장하고 우리의 카운터 값이 동일하게 유지되도록 합니다.

언제 @StateObject를 사용해야 하나요?

뷰가 언제든지 생성되거나 다시 생성될 수 있으므로 뷰 내에서 @ObservedObject를 생성하는 것은 안전하지 않습니다. @ObservedObject를 의존성으로 주입하지 않는 한 뷰를 다시 그릴 때 일관된 결과를 보장하기 위해 @StateObject 래퍼를 사용하려고 합니다.

모든 뷰에 @StateObject를 사용해야 하나요?

동일한 @StateObject 인스턴스를 사용하는 모든 뷰에 대해 이 속성 래퍼를 사용할 필요는 없습니다. 객체의 생명주기를 두 곳에서 유지 및 관리하도록 요청하기 때문에 이를 피해야 합니다.

결론

@StateObject와 @ObservedObject는 유사한 특징을 가지고 있지만 SwiftUI가 생명주기를 관리하는 방식에서 차이가 있습니다. 현재 뷰가 관찰된 객체를 생성할 때 일관된 결과를 보장하기 위해 상태 객체 속성 래퍼를 사용하십시오. 의존성으로 관찰된 객체를 주입할 때 @ObservedObject를 사용할 수 있습니다.

 

반응형