본문 바로가기
iOS

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

by GODOLs 2023. 11. 5.

목차

    지난번에는 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를 사용할 수 있습니다.

     

    반응형