티스토리 뷰

안녕하세요.

이전 글에선 @State, @Binding, @Published 3가지 Property Wrapper에 대해 알아보았고,

이번엔 @ObservedObject, @StateObject에 대해 알아보겠습니다.

@ObservedObject와 @StateObject

A property wrapper type that subscribes to an observable object and invalidates a view whenever the observable object changes.
출처: 공식문서

둘은 비슷한 상황에 사용되고, 공통점이 많기 때문에 같이 정리해 보겠습니다.

@ObservedObject와 @StateObject는 ViewModel 객체와 같은 반응형 객체를 만들 때 사용되는 Property Wrapper입니다.

두 Property Wrapper를 사용하려면, 해당 객체는 아래와 같이 ObservableObject 프로토콜을 준수하는 클래스 객체여야 합니다.

class DataModel: ObservableObject {
    @Published var name = "Some Name"
}

@ObservedObject와 @StateObject는 해당 객체를 SwiftUI에서 View가 관찰 가능한 객체로 만들어 주며, 데이터 변경에 따른 UI를 다시 그릴 수 있게 해줍니다.

ObservableObject 객체는 View가 변경에 감지해야 할 프로퍼티를 이전에 다뤘던 @Published Property Wrapper를 통해 선언해 줍니다.

또는, @Published로 선언하지 않고, 아래와 같이

class DataModel: ObservableObject {
    var name = "Some Name"
    
    func changeName(name: String) {
        self.name = name
        objectWillChange.send()
    }
}

objectWillChange.send() 메서드를 호출해 ObservableObject 객체의 상태값이 변경되었다는 것을 수동으로 View에게 전달할 수 있습니다.(기본적으로 @Published 사용함.)

그럼 차이점은 무엇일까?

@ObservedObject와 @StateObject의 차이점

@ObservedObject는 아래와 같이 View 객체 내부에서 인스턴스화할 경우 한 가지 문제가 발생합니다.

@ObservedObject로 선언한 객체의 경우 해당 View가 그려질 때 같이 초기화됩니다.

코드를 통해 확인해 보면,

struct SubView: View {
    @ObservedObject private var dataModel = DataModel()
    var body: some View {
        VStack {
            Text("이름: \(dataModel.name)")
            Button("랜덤 이름") {
                dataModel.changeName(name: "ABC\(Int.random(in: 1...1000))")
            }
        }
    }
}

class DataModel: ObservableObject {
    @Published private(set) var name = "Some Name"
    
    func changeName(name: String) {
        self.name = name
    }
}

SubView가 있고, 이름을 데이터를 관리하는 @ObservedObject로 선언된 DataModel 객체가 있습니다.

struct ContentView: View {
    @State private var code: Int = 0
    
    var body: some View {
        VStack {
            Text("코드: \(code)")
            SubView()
            Button("랜덤 코드") {
                code = Int.random(in: 1...1000)
            }
        }
        .padding()
    }
}

ContentView 내부에는 위에서 만든 SubView가 있고, 코드값을 보여주는 TextView와 코드를 변경해주는 Button이 있습니다.

이름을 변경하다가 코드가 변경되면 이름값이 초기화되는 것을 확인할 수 있습니다.

위와 같은 문제를 해결하기 위해 2가지 방법이 있는데

  • @ObservedObject 객체를 부모 뷰에서 주입시켜주는 방법
  • @StateObject를 사용하는 방법

첫번째를 해보면, 

struct ContentView: View {
    @State private var code: Int = 0
    
    @ObservedObject private var dataModel = DataModel()
    var body: some View {
        VStack {
            Text("코드: \(code)")
            SubView(dataModel: dataModel)
            Button("랜덤 코드") {
                code = Int.random(in: 1...1000)
            }
        }
        .padding()
    }
}

struct SubView: View {
    @ObservedObject private var dataModel: DataModel
    
    init(dataModel: DataModel) {
        self.dataModel = dataModel
    }
    
    var body: some View {
        VStack {
            Text("이름: \(dataModel.name)")
            Button("랜덤 이름") {
                dataModel.changeName(name: "ABC\(Int.random(in: 1...1000))")
            }
        }
    }
}

위와 같이 DataModel을 부모뷰로 부터 주입받도록 수정했습니다.

위와 같이 코드가 변경될 때 이름이 초기화되지 않습니다.

두 번째도 해보면,

struct SubView: View {
    @StateObject private var dataModel = DataModel()
    var body: some View {
        VStack {
            Text("이름: \(dataModel.name)")
            Button("랜덤 이름") {
                dataModel.changeName(name: "ABC\(Int.random(in: 1...1000))")
            }
        }
    }
}

이번엔 @StateObject로 선언하였습니다.

똑같이 문제를 해결할 수 있었습니다.

즉, 정리해 보자면,

외부에서 주입을 통해 ObservableObject 객체를 받는다면, @ObservedObject 사용을 권장 드리고,

내부에서 인스턴스화해서 사용한다면, 안전하게 @StateObject 사용을 권장 드립니다.

여기까지 @ObservedObject와 @StateObject에 대해 알아보았습니다.

원래 2편을 기획했는데 양 조절을 실패했네요.

다음 편에서 @Environment에 대해 알아보겠습니다.

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함