공통점
- 어떠한 목적을 가지고(로직분리, 테스트, 값 저장..) 유저 인터페이스에서 코드를 분리하기 위해 사용한다.
- 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()
참고
- https://developer.apple.com/documentation/swiftui/stateobject
- https://developer.apple.com/documentation/swiftui/observedobject
- https://fatbobman.medium.com/ask-apple-2022-q-a-related-to-swiftui-part-1-cf3368a66e32
- https://www.swiftbysundell.com/articles/swiftui-state-management-guide/
- https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app
- https://developer.apple.com/documentation/Combine/ObservableObject
- https://developer.apple.com/documentation/swiftui/state/init(wrappedvalue:)
'SwiftUI' 카테고리의 다른 글
@State값을 ModalView(FullScreen, sheet..) 에 주입시 유의점 (0) | 2024.01.06 |
---|---|
Building Custom View with SwiftUI(WWDC19) - 2 (0) | 2022.11.12 |
Building Custom View with SwiftUI(WWDC19) - 1 (0) | 2022.11.12 |
댓글