본문 바로가기
SwiftUI

@StateObject vs @ObservedObject

by SeoB-P 2024. 1. 2.

공통점

  • 어떠한 목적을 가지고(로직분리, 테스트, 값 저장..) 유저 인터페이스에서 코드를 분리하기 위해 사용한다.
  • ObservableObject 를 채택하고 있다.

차이점

가장 큰 차이점은 View가 재생성 될때 나타난다.

SwiftUI는 매우 빈번하게 View를 재생성하게 되는데, 이때 View안에서 @ObservaedObject를 생성하게 되면

View가 재생성 될때마다 같이 초기화가 되기 때문에 예상치 못한 결과를 얻을 수 있다.

이를 해결하기 위해 apple은 @StateObject를 추가한 것 같다.

@StateObject는 주어진 View 인스턴스가 얼마나 재생성 되는지에 상관없이 관리 할 수 있게 해준다.

struct LibraryView: View {
    @ObservedObject private var book = Book() // View가 재생성될때마다 초기화됨.
    @StateObject private var book = Book()    // View자체에 종속되어 재생성 되지 않음.
    
    var body: some View {
        BookView(book: book)
    }
}

위 코드는 똑같은 결과를 가져오는 것 처럼 보이지만 매우 다르다.

궁금점 및 유의점

모든 View안의 ObservableObject를 @StateObject로 생성하는게 좋지 않나?

그렇지 않다.

StateObject는 주어진 View인스턴스에 종속되기 때문에 StateObject를 재생성하여 바꾸고 싶을때는

오히려 정상 작동을 하지 않을 수 있다.

 

예시 코드

// 부모 뷰
struct StateTestView: View {
    @State var name = ""
    @State var age = 0

    var body: some View {
        let _  = Self._printChanges()
        Group {
            SubViewWithStateObject(name: name, age: age)

            Button("change State Object on parent View") {
                self.name = "parent"
                self.age = Int.random(in: 0...100)
            }
            
            Button("print on parent") {
                print(name)
                print(age)
            }

        }
    }
}

// 자식 뷰
struct SubViewWithStateObject: View {
    @StateObject var testStateObject: TestStateObject

// 주어진 값을 이용, 새로운 StateObject를 생성
    init(name: String, age: Int) {
        self._testStateObject = .init(wrappedValue: TestStateObject(name: name, age: age))
    }

    var body: some View {
        let _  = Self._printChanges()
        VStack {
            Text("\(testStateObject.name)")
            Text("\(testStateObject.age)")
        }

        Button("change state object") {
            self.testStateObject.name = "Piggy"
            self.testStateObject.age = Int.random(in: 0...100)
        }

        Button("print!") {
            print(self.testStateObject.name)
            print(self.testStateObject.age)
        }
    }
}


class TestStateObject: ObservableObject {
    @Published var name: String
    @Published var age: Int

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

위 코드를 실행해보면 부모뷰의 값이 바뀌었음에도 SubView의 StateObject는 바뀌지 않는다.

결과적으로 View도 원했던 방향으로 바뀌지 않는다.

testStateObject 의 타입을 @ObservedObject로 바꾸면, 정상적으로 값이 바뀜을 확인할 수 있다.

왜냐하면, @ObservedObject는 View에 종속되지 않고 매번 재생성이 가능하기 때문.

Custom으로 ID를 주면 ObservedObject를 사용하지 않아도 사용이 가능하긴하다.

 SubViewWithStateObject(name: name, age: age).id(UUID().uuidString)

결론

  • ObservableObject 프로토콜을 따르는 데이터 모델을 재생성해야할 필요가 있을때에는 ObservedObject 지향
  • 특정 View에 종속되어 모델 인스턴스를 재생성할 필요가 없을때에는 StateObject 지향
  • 일반적인 경우라면 View내부에서 다음과 같이 ObservableObject 초기화 지양
    • @StateObject var testStateObject: TestStateObject
    • @ObservedObject var testStateObject: TestObservedObject = .init()

참고

댓글