@State, View Rendering
firstNumber 변경 - 별도의 구조체 (let)
struct ContentView: View {
@State private var firstNumber = 0
@State private var secondNumber = 0
@State private var thirdNumber = 0
@State private var forthNumber = 0
var body: some View {
VStack {
Text("First: \\(firstNumber)").background(Color.random())
second
third()
FourthView(fourthNumber: forthNumber)
Button("Number Change Button") {
firstNumber = Int.random(in: 1...100)
}
.buttonStyle(.borderedProminent)
.padding()
}
.font(.title)
}
var second: some View {
Text("Second: \\(secondNumber)").background(Color.random())
}
func third() -> some View {
Text("Third \\(thirdNumber)").background(Color.random())
}
}
struct FourthView: View {
let fourthNumber: Int
var body: some View {
Text("Fourth: \\(fourthNumber)").background(Color.random())
}
}
- firstNumber만 변경했음에도 secondNumber(프로퍼티)와 thirdNumber(메서드)도 렌더링되면서 백그라운드의 컬러가 변경
- 별도의 구조체로 설정한 fourthNumber는 렌더링되지 않기에 백그라운드의 컬러가 유지
firstNumber 변경 - 별도의 구조체 (@Binding var)
struct FourthView: View {
@Binding var fourthNumber: Int
var body: some View {
Text("Fourth: \\(fourthNumber)").background(Color.random())
}
}
- let으로 설정했을 때와 동일한 결과
- firstNumber만 변경했음에도 secondNumber(프로퍼티)와 thirdNumber(메서드)도 렌더링되면서 백그라운드의 컬러가 변경
- 별도의 구조체로 설정한 fourthNumber는 렌더링되지 않기에 백그라운드의 컬러가 유지
fourthNumber 변경 - 별도의 구조체 (let)
struct ContentView: View {
@State private var firstNumber = 0
@State private var secondNumber = 0
@State private var thirdNumber = 0
@State private var forthNumber = 0
var body: some View {
VStack {
Text("First: \\(firstNumber)").background(Color.random())
second
third()
FourthView(fourthNumber: forthNumber)
Button("Number Change Button") {
forthNumber = Int.random(in: 1...100)
}
.buttonStyle(.borderedProminent)
.padding()
}
.font(.title)
}
var second: some View {
Text("Second: \\(secondNumber)").background(Color.random())
}
func third() -> some View {
Text("Third \\(thirdNumber)").background(Color.random())
}
}
struct FourthView: View {
let fourthNumber: Int
var body: some View {
Text("Fourth: \\(fourthNumber)").background(Color.random())
}
}
- fourthNumber만 변경되었음에도 firstNumber, secondNumber, thridNumber가 전부 렌더링되면서 백그라운드의 컬러가 변경
fourthNumber 변경 - 별도의 구조체 (@Binding var)
struct FourthView: View {
@Binding var fourthNumber: Int
var body: some View {
Text("Fourth: \\(fourthNumber)").background(Color.random())
}
}
- 별도의 구조체만 렌더링 되고 다른 요소들은 렌더링되지 않아 백그라운드의 컬러가 변경되지 않는 것을 알 수 있음
@Published, View Rendering …with ViewModel
firstNumber 변경 - 별도의 구조체 (let)
class ContentViewModel: ObservableObject {
@Published var firstNumber: Int = 0
@Published var secondNumber: Int = 0
@Published var thirdNumber: Int = 0
@Published var fourthNumber: Int = 0
}
struct ContentView: View {
@StateObject var viewModel: ContentViewModel = ContentViewModel()
var body: some View {
VStack {
Text("First: \\(viewModel.firstNumber)")
.background(Color.random())
second
third()
FourthView(fourthNumber: viewModel.fourthNumber)
Button("Number Change Button") {
viewModel.firstNumber = Int.random(in: 1...100)
}
.buttonStyle(.borderedProminent)
.padding()
}
.font(.title)
}
var second: some View {
Text("Second: \\(viewModel.secondNumber)")
.background(Color.random())
}
func third() -> some View {
Text("Third \\(viewModel.thirdNumber)")
.background(Color.random())
}
}
struct FourthView: View {
let fourthNumber: Int
var body: some View {
Text("Fourth: \\(fourthNumber)")
.background(Color.random())
}
}
- 뷰모델이 존재하지 않았을 때와 동일
- firstNumber만 변경했음에도 secondNumber(프로퍼티)와 thirdNumber(메서드)도 렌더링되면서 백그라운드의 컬러가 변경
- 별도의 구조체로 설정한 fourthNumber는 렌더링되지 않기에 백그라운드의 컬러가 유지
firstNumber 변경 - 별도의 구조체 (@Binding var)
struct FourthView: View {
@Binding var fourthNumber: Int
var body: some View {
Text("Fourth: \\(fourthNumber)").background(Color.random())
}
}
- 뷰모델이 존재하지 않았을 때와 동일
- let으로 설정했을 때와 동일한 결과
- firstNumber만 변경했음에도 secondNumber(프로퍼티)와 thirdNumber(메서드)도 렌더링되면서 백그라운드의 컬러가 변경
- 별도의 구조체로 설정한 fourthNumber는 렌더링되지 않기에 백그라운드의 컬러가 유지
fourthNumber 변경 - 별도의 구조체 (let)
struct ContentView: View {
@StateObject var viewModel: ContentViewModel = ContentViewModel()
var body: some View {
VStack {
Text("First: \\(viewModel.firstNumber)")
.background(Color.random())
second
third()
FourthView(fourthNumber: viewModel.fourthNumber)
Button("Number Change Button") {
viewModel.fourthNumber = Int.random(in: 1...100)
}
.buttonStyle(.borderedProminent)
.padding()
}
.font(.title)
}
var second: some View {
Text("Second: \\(viewModel.secondNumber)")
.background(Color.random())
}
func third() -> some View {
Text("Third \\(viewModel.thirdNumber)")
.background(Color.random())
}
}
struct FourthView: View {
let fourthNumber: Int
var body: some View {
Text("Fourth: \\(fourthNumber)")
.background(Color.random())
}
}
- 뷰모델이 존재하지 않았을 때와 동일
- fourthNumber만 변경되었음에도 firstNumber, secondNumber, thridNumber가 전부 렌더링되면서 백그라운드의 컬러가 변경
fourthNumber 변경 - 별도의 구조체 (@Binding var) ‼️
struct FourthView: View {
@Binding var fourthNumber: Int
var body: some View {
Text("Fourth: \\(fourthNumber)").background(Color.random())
}
}
- 뷰모델로 관리할 경우 별도의 구조체의 바인딩을 해도 다른 요소들이 함께 렌더링되어 모든 요소들의 백그라운드 컬러가 변경되게 됨
- 별도의 구조체로 관리하고 @Binding으로 값을 전달받았음에도 왜 독립적으로 렌더링되지 않는지 (아래 설명)
@State와 @Published의 Binding 동작 차이
@State와 @Published는 SwiftUI에서 상태 관리를 위해 사용되지만, 렌더링 범위와 방식에 차이가 존재.
@State는 뷰 내부의 로컬 상태 관리에 최적화되어 있고, @Published는 뷰 모델과 같은 외부 객체에서 상태를 관찰할 때 사용.
1. @State로 관리할 때 바인딩된 하위 뷰만 렌더링
@State는 SwiftUI 내부에서 뷰의 로컬 상태로 관리되며, SwiftUI가 이 상태 변경을 아주 정교하게 추적.
- @State는 뷰 계층 구조와 직접 연결.
- 부모 뷰에서 @State로 관리하는 값이 변경되더라도, 바인딩된 하위 뷰만 해당 상태를 관찰하고 있으므로, 그 하위 뷰만 다시 렌더링.
- SwiftUI는 상태 변경이 정확히 어느 뷰와 연결되어 있는지를 알고 최적화.
2. @Published로 관리할 때 상위 뷰 전체가 렌더링
@Published는 옵저버블 상태 관리 프로퍼티로, SwiftUI와 결합하여 @StateObject나 @ObservedObject로 사용.
- @Published는 뷰 모델에서 상태가 변경될 때, SwiftUI에 전체적으로 상태가 변경되었다는 신호를 보냄.
- SwiftUI는 뷰 모델(@StateObject)과 연결된 최상위 뷰 전체의 body를 다시 계산.
- SwiftUI는 @Published의 변경 범위를 정확히 추적하지 못하기 때문에, 관련된 모든 뷰를 다시 렌더링.
3. 왜 이런 차이가 있을까?
SwiftUI의 상태 추적 방식
- @State는 SwiftUI가 직접 관리하기 때문에, 특정 상태와 뷰의 연결을 정밀하게 추적.
- 변경 범위를 최소화하여, 필요한 뷰만 다시 렌더링.
- @Published는 Combine 프레임워크의 옵저버블 객체로 동작하며, SwiftUI는 이 객체의 상태 변경 범위를 세밀하게 추적할 수 없음.
- 대신, 뷰 모델의 objectWillChange 신호를 통해 전체 뷰를 다시 계산.
Binding의 동작 차이
- @Binding은 부모의 특정 @State를 참조하므로, SwiftUI가 정확히 어느 뷰와 연결되었는지를 알고, 변경된 하위 뷰만 다시 렌더링.
- 반면, @Binding이 @Published 상태를 참조하는 경우, SwiftUI는 @StateObject와 연결된 부모 뷰 전체를 다시 렌더링.
렌더링과 init의 차이
- SwiftUI는 상태 변경 시마다 뷰를 다시 렌더링하지만, 별도의 구조체의 init은 항상 호출.
- 즉, 렌더링이 발생하지 않아도 뷰 구조체의 초기화는 수행.
- init에서 네트워크 통신이나 복잡한 작업이 있다면 성능 문제로 이어질 수 있음.