홍로그

@StateObject, @ObservedObject, @EnvironmentObject 비교 본문

iOS

@StateObject, @ObservedObject, @EnvironmentObject 비교

성홍민 2023. 6. 3. 12:12

📖 @StateObject vs @ObservedObject vs @EnvironmentObject

SwiftUI에서 데이터 흐름은 뷰들이 데이터에 의존하고, 상태 변화가 생기면 뷰를 다시 그려야 하는 것에 대한 대응 방식입니다. 이런 스타일을 "선언적" 프로그래밍이라고 합니다. 이때, 속성 선언자들은 상태 변화를 뷰에 알리고 이를 관리하기 위한 역할을 담당합니다. 이러한 기능을 구현하기 위해서 ObservableObject 프로콜이 사용됩니다. ObservableObject 프로토콜은 해당 객체에 변화가 생길 때 뷰에 알릴 수 있는 기능을 제공합니다. Wrappers(@StateObject, @ObservedObject, @EnvironmentObject) 모두 ObservableObject를 다루는 용도로 사용됩니다.

1. @StateObject

  • @StateObject는 뷰의 소유권이 있는 객체를 정의할 때 사용합니다. 이는 뷰의 상태를 추적하고 변화를 감지하면서, 뷰의 그림을 업데이트해 주는 역할을 합니다.
  • @StateObject는 뷰의 소유권이 있는 객체의 수명 주기를 관리하며, 뷰를 재생성해도 객체가 유지됩니다.

2. @ObservedObject

  • @ObservedObject는 뷰 인스턴스 특정 영역에서만 사용하는 외부에서 이미 생성된 소유권이 없는 객체를 참조할 때 사용합니다. 이렇게 전달받은 객체는 뷰 간에 참조를 전달해 상태를 공유 수 있게 해 줍니다.
  • 객체의 수명 주기를 관리하지 않습니다.
  • 뷰 바인딩을 위한 바인딩 콘텍스트를 생성하여 데이터를 관측합니다. 뷰 인스턴스가 소멸되면 참조하는 객체에 대한 상태 추적도 종료됩니다.

예시코드

ObservableObject를 구현한 TimerData Class

class TimerData: ObservableObject {
    @Published var timeCount = 0
    private var timer: Timer?
    
    func startTimer() {
        self.timer?.invalidate()
        self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
            self.timeCount += 1
        }
    }
    
    func stopTimer() {
        self.timer?.invalidate()
        self.timer = nil
    }
}

다음으로 두 개의 뷰가 있습니다.

  1. ParentView - TimerData를 생성하고, 자식 뷰인 ChildView에게 객체 참조를 전달합니다.
  2. ChildView - 실제로 TimerData를 사용하여 타이머를 시작 및 중지합니다.
struct ParentView: View {
    @StateObject private var timerData = TimerData()
    
    var body: some View {
        VStack {
            Text("Parent View")
            Text("Time: \(timerData.timeCount)")
            ChildView(timerData: timerData)
        }
    }
}

struct ChildView: View {
    // 소유권이 없으며 부모 뷰로부터 전달받은 객체를 참조합니다.
    @ObservedObject var timerData: TimerData
    
    var body: some View {
        VStack {
            Text("Child View")
            Button("Start Timer") {
                timerData.startTimer()
            }
            Button("Stop Timer") {
                timerData.stopTimer()
            }
        }
    }
}

3. @Environment

  • 애플리케이션 전체에서 공유되어야 하는 객체를 관리할 때 사용됩니다. 예를 들어, 계정 정보와 같은 사용자 데이터를 다룰 때 사용할 수 있습니다.
  • 주로 상위 뷰에서 객체를 설정하고 하위 뷰들이 이 객체를 자유롭게 사용할 수 있도록 합니다. 이때 뷰 계층 구조와 상관없이 사용할 수 있습니다.
  • 인스턴스를 주입받을 때는 손쉽게 YourView().environmentObject(yourObject)와 같이 설정할 수 있습니다.

예시코드

import SwiftUI

struct ParentView: View {
    @EnvironmentObject var timerData: TimerData
    
    var body: some View {
        VStack {
            Text("Parent View")
            Text("Time: \(timerData.timeCount)")
            ChildView()
        }
    }
}

struct ChildView: View {
    @EnvironmentObject var timerData: TimerData
    
    var body: some View {
        VStack {
            Text("Child View")
            Button("Start Timer") {
                timerData.startTimer()
            }
            Button("Stop Timer") {
                timerData.stopTimer()
            }
        }
    }
}

 

import SwiftUI

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ParentView()
                .environmentObject(TimerData()) // 타이머 데이터 객체를 환경 객체로 설정합니다.
        }
    }
}

4. 의문점

거의 모든 경우 @EnvironmentObject로 해결할 수 있을 것 같은데 언제 @StateObject와 @ObservedObject를 사용해야 할까?

  • @StateObject @ObservedObject를 사용하는 경우:
  • 뷰의 로컬 상태를 관리하고, 상위 뷰에서 명시적으로 데이터를 전달해야 하는 뷰 계층 구조에 사용됩니다. 일반적으로 하나의 뷰와 관련된 상태를 처리할 때 주로 사용됩니다.
  • 뷰의 소유자가 상태 개체를 만들고, 해당 상태 객체의 라이프 사이클이 뷰와 연결되는 경우에 사용합니다.
  • 상황에 따라 하위 뷰에 데이터를 직접 전달해야할 때 사용합니다. 이 경우 상위 뷰에서 @StateObject를 생성하고, 하위 뷰에서 @ObservedObject를 사용하여 상위 뷰의 상태를 참조합니다.
  • 뷰 계층 구조 동안 명확한 통신과 더 간결한 데이터 관리를 제공할 수 있습니다. 이를 통해 전체 앱 인스턴스에 영향을 주지 않고 독립적이며 재사용 가능한 뷰 컴포넌트를 만들 수 있습니다.
  • 앱의 특정 뷰와 관련된 데이터를 실수로 업데이트하지 않도록 데이터의 범위를 제한하는 데 도움이 됩니다.
  • @EnvironmentObject를 사용하는 경우:
  • 여러 뷰에서 데이터를 공유하는 데 사용되며, 앱 전체에 걸쳐 해당 상태를 자동으로 공유하고 싶을 때 사용됩니다. 앱 설정, 사용자 환경설정, 서비스 등과 같은 공통된 데이터를 저장할 때 유용합니다.
  • 앱의 많은 부분과 여러 뷰 계층 구조에 걸쳐 하나 객체를 공유해야 하는 경우에 사용합니다.
  • @EnvironmentObject를 사용하면 상위 뷰에서 하위 뷰로 명시적으로 데이터를 전달할 필요 없이 자동으로 공유됩니다.
  • @ObservedObject를 사용해야 하는 경우에 @EnvironmentObject를 사용하면 발생하는 문제점
  • 앱 전체의 뷰 계층 구조에서 데이터를 공유하기 때문에 불필요하게 광범위한 데이터 공유가 발생할 수 있습니다. 이로 인해 특정 뷰에만 적용되어야 하는 데이터가 다른 뷰에서도 접근 가능해지므로 코드의 복잡성이 증가합니다.

5. 결론

상황과 목적에 따라 적절한 뷰 프로퍼티 래퍼를 선택해야 합니다. 하나의 화면 내의 부모뷰, 자식뷰가 있는 경우에 대한 상태를 관리하려는 경우 @ObservedObject, 여러 화면 또는 전체 앱에서 공유하는 데이터를 관리하려는 경우 @EnvironmentObject를 사용하는 것이 권장됩니다. 또한, 상태 객체의 라이프 사이클을 조금 더 명확하게 관리하려면 @StateObject로 사용할 수 있습니다.

6. 참고

@StateObject 애플 문서

@ObservedObject 애플 문서

@EnvironmentObject 애플 문서

 

 

 

반응형

'iOS' 카테고리의 다른 글

App Thinning  (0) 2023.06.05
MVP 패턴  (2) 2023.06.04
데이터 바인딩(Data Binding)  (0) 2023.06.02
SwiftUI @State  (0) 2023.06.01
MVVM 패턴  (0) 2023.05.31