일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- Network
- Animation
- stateobject
- environmentobjet
- Concurrency
- auth
- combine
- authentication
- swift
- state
- 데이터최적화
- WWDC
- dataflow
- GCD
- Performance
- arkit
- RxSwift
- CS
- 네트워크
- withAnimation
- iphone
- ar
- realitykit
- ios
- gesture
- fullscreencover
- SwiftUI
- 달력
- firebase
- UIKit
- Today
- Total
XLOG
[DataFlow] State, Binding, StateObject, ObservedObject, EnvironmentObject 는 무엇이고 차이는? 본문
[DataFlow] State, Binding, StateObject, ObservedObject, EnvironmentObject 는 무엇이고 차이는?
X_PROFIT 2024. 7. 1. 13:151. State
Single source of truth 로 View 에서 PropertyWrapper 로 정의 한다. 그로 인해 View 와 따로 독립적으로 SwiftUI 가 데이터를 관리해 주며 생명 주기는 View 의 생명주기와 동일하다. State 값의 변화를 view에 알려주면 View 는 re-render 한다.
단 해당 State 가 정의된 View 의 body 내부에 직접적으로 state 값을 활용하여 View 의 내용이 달라지는 요소가 없다면 re-render 하지 않는다.
import SwiftUI
struct ParentView: View {
@State var count: Int = 0
var body: some View {
VStack {
let _ = Self._printChanges()
Text("Text")
.padding()
.background(Color.randomColor)
Text("\(count)")
.padding()
.background(Color.randomColor)
Button(action: { count += 1 }) {
Text("+")
.padding()
.background(Color.randomColor)
}
}
.padding()
.background(Color.randomColor)
}
}
View 는 정상적으로 state 값의 변화를 확인하고 body 전체를 re-render 해준다. 하지만 Text("\(count)") 부분을 뺀다면...
import SwiftUI
struct ParentView: View {
@State var count: Int = 0
var body: some View {
VStack {
let _ = Self._printChanges()
Text("Text")
.padding()
.background(Color.randomColor)
Button(action: {
print("call button action")
count += 1
}) {
Text("+")
.padding()
.background(Color.randomColor)
}
}
.padding()
.background(Color.randomColor)
}
}
body 내부 view에서 button action을 제외하고 count 를 사용하지 않기에 view 에서는 state 값의 변화를 catch 하지 못하고 re-render를 하지 않는 것을 확인할 수 있다.
2. Binding
Binding 은 State 로 정의한 SSOT(Single source of truth) 의 값을 하위 뷰로 전달할 때 사용한다. 하위 View에 State PropertyWrapper 를 사용하게 되면 하위 뷰도 독립적이 Source of truth 를 생성하지만 Binding 으로 정의하여 값을 전달받게 되면 상위View 의 Source of truth 를 바라보게 된다.
struct ParentView: View {
@State private var count: Int = 0
var body: some View {
ChildView(count: $count)
}
}
struct ChildView: View {
@Binding var count: Int
var body: some View {
Text("\(count)")
}
}
3. StateObject
ObservableObject의 State 버전이라고 생각할 수 있다. 생명주기도 View 와 동일하다. ObservableObject 내부에 Published 프로퍼티의 값의 변화가 발생하면 SwiftUI 는 View 를 업데이트 시킨다. 하지만 확인을 하는 과정 중에 알게된 사실인데, StateObject 는 State 와 달리, Published 값의 변화가 있다면 무조건 view 를 update(re-render) 시킨다.
import SwiftUI
struct ParentView: View {
@StateObject var vm: PracticeViewModel = PracticeViewModel()
var body: some View {
VStack {
let _ = Self._printChanges()
Text("Text")
.padding()
.background(Color.randomColor)
Button(action: {
print("call button action")
vm.count += 1
}) {
Text("+")
.padding()
.background(Color.randomColor)
}
}
.padding()
.background(Color.randomColor)
}
}
4. ObservedObject
View 내부에서 ObservableObject 의 Published property 의 변화를 View에 알려 View를 업데이트 시킬 때 사용한다. 하지만 StateObject 와의 차이가 있다. StateObject 의 경우 따로 데이터를 관리하며 View의 생명주기와 같은 생명주기를 갖는다. 하지만 ObservedObject 의 경우 re-render 될 때마다 새로운 값을 할당 받는다. 그래서 주로 StateObject 를 하위View의 값을 전달할 때 사용한다. 즉 Binding 과 비슷하다.
import SwiftUI
struct ParentView: View {
@StateObject var vm: PracticeViewModel = PracticeViewModel()
var body: some View {
VStack {
let _ = Self._printChanges()
Text("Text")
.padding()
.background(Color.randomColor)
Button(action: {
print("call button action")
vm.count += 1
}) {
Text("+")
.padding()
.background(Color.randomColor)
}
ObservedObjectChildView()
}
.padding()
.background(Color.randomColor)
}
}
struct ObservedObjectChildView: View {
@ObservedObject var vm: PracticeViewModel
init(vm: PracticeViewModel? = nil) {
print("ChildView init")
self.vm = vm ?? PracticeViewModel()
}
var body: some View {
VStack {
let _ = Self._printChanges()
Text("Child View : \(vm.count)")
.padding()
.background(Color.randomColor)
Button(action: {
print("call button action")
vm.count += 1
}) {
Text("+")
.padding()
.background(Color.randomColor)
}
}
}
}
child View 의 init 이 불리며 ObservedObject 의 값이 새로 할당을 받게 되고, 그러면서 View 도 update 가 되게 된다.
struct ObservedObjectChildView: View {
@ObservedObject var vm: PracticeViewModel
init(vm: StateObject<PracticeViewModel>? = nil) {
print("ChildView init")
self._vm = vm ?? StateObject(wrappedValue: PracticeViewModel())
}
var body: some View {
'''
'''
}
}
StateObject 의 경우 childView의 init 이 불리더라도 View 에서는 ViewModel 의 변화를 감지하지 못하고, 값 또한 유지 되는 것을 볼 수 있다.
5. EnvironmentObject
ObservableObject 를 View 계층 내부에서 전역적으로 사용할 수 있게 해준다.
import SwiftUI
@main
struct DataFlowPracticeApp: App {
@StateObject private var vm = PracticeViewModel()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(vm)
}
}
}
struct ContentView: View {
@EnvironmentObject var vm: PracticeViewModel
var body: some View {
VStack {
let _ = Self._printChanges()
Button(action: {
print("call button action")
vm.count += 1
}) {
Text("+")
.padding()
.background(Color.randomColor)
}
Text("\(vm.count)")
.padding()
.background(Color.randomColor)
}
.padding()
.background(Color.randomColor)
}
}
ContentView 하위 View 어디에서든지 @EnvironmentObject 를 통해 접근이 가능하다.
'Swift > SwiftUI' 카테고리의 다른 글
[SwiftUI] Observation (feat iOS 17, 써야하는 이유) (2) | 2024.08.17 |
---|---|
[최적화] View 의 Update 를 최소화 하는 방법 (0) | 2024.07.01 |
[SwiftUI] Environment 활용하기 (0) | 2024.06.25 |
[SwiftUI] 밀리의 서재 책정보 Sheet 애니메이션 아이디어 및 구현 (0) | 2024.06.24 |
[SwiftUI] View 의 Size 구하기 (0) | 2024.05.19 |