맛동산이

SwiftUI) ObservableObject, ObservedObject, SateObject, Published 에 관하여 본문

앱/SwiftUI

SwiftUI) ObservableObject, ObservedObject, SateObject, Published 에 관하여

진ddang 2023. 7. 5. 01:12

먼저 SwiftUI는 반응형프로그래밍을 하기 위한 프레임 워크라는것을 이해하면 좋다.

기존의 MVC가 아닌 MVVM으로 프로그래밍을 할때 중요한것은 ViewModel과 View가 어떻게 연결되어있고, 서로 관찰하는지에 대해서 고민을 해야하며, 해당 데이터 바인딩을 어떻게 해줄지가 중요하다.

기존의 UIKit에서는 이러한 문제점을 RxSwift라는 3th party library를 사용해서 해결하였는데, (최근에는 콤바인등… )

SwiftUI는 기본적으로 잘 설계되어 있어서 이러한 데이터 플로우에 대해서 이해하는 것이 굉장히 중요하다고 생각한다.

따라서 오늘은 ObservableObject, ObservedObject, StateObject에 관해서 공부를 해보자!

ObservableObject

ObservableObject는 프로토콜이다.

해당프로토콜을 어떠한 Object(객체) 가 채택하게 되면, 해당 객체는 Publisher를 가지게 된다.

Publisher는 해당객체에 어떠한 변화를 감지하게 되면, 해당 오브젝트를 바라보고있는, 뷰에 이벤트를 방출하게 되고, 뷰는 refresh된다.

이렇게, 뷰가 해당객체를 관찰할수 있는 객체로 만들기 위해서 채택하는 프로토콜이다.

ObservedObject

ObservedObject는 위에서 ObservableObject를 채택한 클래스나, 구조체를 뷰에서 관찰할수 있도록 인스턴스화 할때, 해당 객체는 ViewModel을 계속해서 관찰하고 있다고 알려주는 property wrapper type이다.

즉, ObservedObject로 레핑한 인스턴스는, ObservableObject로 선언된, ViewModel을 계속해서 관찰하고 있으며 해당 뷰 모델값이 변경될때, View를 리프레시 해준다.

예제코드

이러한 뷰를 만들었을때 코드는 다음과 같다.

struct ContentView: View {
    @State var isPresented = false
    @State var otherNumber = 0
    
    var body: some View {
        VStack {
            Text("Number: \(otherNumber)")
            newNumberAdd
            CountView()
        }
    }
    var newNumberAdd: some View {
        Button {
            otherNumber += 1
        } label: {
            Text("nonObserved")
        }
    }
}

struct CountView: View {
    
    @ObservedObject var viewModel = NumberVM()
    
    var body: some View {
        VStack {
            Text("otherNumber : \(viewModel.count)")
            addButton
        }
    }
    var addButton: some View {
        Button {
            viewModel.add()
        } label: {
            Text("ObservedObject")
        }
    }
}
class NumberVM: ObservableObject {
    @Published var count: Int = 0
    
    init() {
        print("refreshed!")
    }
    
    func add() {
        count += 1
    }
}

뷰모델은 ObservableObject를 채택하고 있으며, @Published var 값을 가지고 있다.


Published????

Published 또한 속성레퍼 구조체이다.

Publisher라는게 아까 위에서 존재한다고 했는데, Publisher가 정확히 어떤일을 하는지에 대해서 논의해 보겠다.

ObservableObject에 존재하는 observableObjectPublisher타입의 objectWillChange라는 프로퍼티를 가지고 있으며, 해당 프로퍼티는 objectWillChange.send()라는 메소드를 사용할수 있다.

해당메소드는 ObservableObject가 변화할때, 해당 변화를 ObservedObject에게 전달해주게 되는 역할을 하는데

이를 일일히 사람의 손으로 컨트롤하게 되면, 너무나도 귀찮기 때문에 Published라는 속성래퍼를 사용한 변수가 변화할때 자동으로 objectWillChange.send()함수를 불러주기 위한 레핑이다.

즉, Published 속성레퍼는, ObservableObject에서 변화를 감지한것을, ObservedObject에 전달하기 위해서 objectWillChange.send()를 자동으로 불러주는 역할을 정해두는것이다.

→ ObservableObject에서 모든 변화를 자동으로 감지하고 보내주는것이 아닌. Published된 프로퍼티의 변화만 emit해줌


돌아와서

해당뷰에서는 지금 nonObserved와 observed의 두개의 뷰가 있는데,

nonObserved버튼을 눌렀을때, 기존의 observed버튼을 통해 올라간 otherNumber이 초기화가 되어버린다.

이러한 이유는

SwiftUI의 데이터플로우 에서는 상위뷰가 refresh되면 하위뷰 또한 모두 refresh된다.

이를 통해서 기존에 기록해뒀던, observedObject인 viewModel또한 리프레시 된것.

viewModel의 초기값은 0 이기 때문에 0이 된것이다.

이를 해결하기 위해서 발생한 방법이 @StateObject이다.

StateObject

StateObject는 인스턴스를 기존의 방법과 동일하게 화면이 초기화 될때 같이 초기화시켜버리는것이 아닌, 해당 인스턴스 값을 계속해서 유지하도록 하는 레핑속성이다.

StateObject로 생성된 객체는 View의 라이프 사이클에 상관없이, SwiftUI가 View와 별개의 메모리 공간에 저장해 데이터를 안전하게 보관하도록 한것

따라서 StateObject는 초기화와 상관없이 메모리가 유지된다.

따라서 위의 상황처럼 상위뷰에서 초기화가 되는 경우에는 이를 해결하기 위해서 StateObject를 사용하면 된다.

뷰 그자체가 폐기되는 상황이라면, 다시 push나 present될때는 초기화가 된다.


참고자료

Swift: ObservedObject와 StateObject
SwiftUI의 ObservedObject와 StateObject가 무엇인지, 두 프로퍼티 래퍼에는 어떤 차이가 있는지 알아보자
https://medium.com/hcleedev/swift-observedobject와-stateobject-4f851ed9ef0d
StateObject | Apple Developer Documentation
A property wrapper type that instantiates an observable object.
https://developer.apple.com/documentation/swiftui/stateobject


Uploaded by N2T

반응형