Source of Truth
UIKit과 SwiftUI의 차이점 중 가장 중요한 부분 중 하나는 SoT(Source of Truth)라고 생각합니다. UIKit에서는 데이터와 UI의 상태를 개발자가 직접 일치시켜줘야 했습니다. 하지만 이 과정에서 실수가 발생하면 데이터와 UI가 서로 다른 상태를 가지는 불일치 문제가 생기고, 이는 예측하기 어려운 버그의 원인이 되었습니다.
하지만 SwiftUI에선 SoT라는 개념을 통해 이러한 문제를 해결하였습니다. SoT는 데이터의 일관성과 정확성을 유지하는 중요한 개념으로, 데이터는 오직 한 곳에서만 관리되고, 모든 UI는 해당 데이터를 바라보며 자신(뷰)을 그립니다. 즉, 데이터가 변경되면 UI는 자동으로 변경되기 때문에, 데이터 불일치 문제가 차단되고, 코드 구조가 단순하고 예측 가능해집니다.
SwiftUI의 Source of Truth
SwiftUI에선 다양한 SoT를 관리할 수 있도록 다양한 프로퍼티 래퍼를 제공합니다.
@State
State는 SwiftUI의 대표적인 SoT로, @State으로 선언하면 Source of Truth가 됩니다. 하위 뷰는 @Binding 등을 통해 같은 데이터 모델을 공유할 수 있게 됩니다. 주로 단일 뷰 내에서 사용되는 간단한 상태를 저장할 때 사용되며, SwiftUI가 @State 프로퍼티의 변경을 감지하면 뷰를 자동으로 다시 그리게됩니다.
@Binding
Binding은 SoT를 직접 소유하지 않으면서 다른 뷰에 있는 SoT를 읽고 수정할 때 사용됩니다. 부모 뷰가 가진 SoT를 자식 뷰에 넘겨줄 때 주로 사용되고, SoT를 참조하고 있기 때문에 자식 뷰에서 값을 변경하게 되면 부모 뷰의 원본 SoT 또한 변경됩니다.
@StateObject & @ObservedObject
여러 뷰에서 공유하고 관찰해야 하는 더 복잡한 데이터 모델(클래스)을 관리할 때 사용되며, 해당 클래스는 ObservableObject 프로토콜을 반드시 채택해야 합니다. 클래스 내부에서 UI 업데이트를 유발하고자 하는 프로퍼티 앞에는 @Published 키워드를 붙여줍니다.
@StateObject와 @ObservedObject는 기능적으로 유사해 보이지만, 객체의 소유권과 생명 주기라는 점에서 매우 중요한 차이점이 있습니다.
- @StateObject: 객체의 소유자(Owner)입니다. SwiftUI는 뷰의 생명 주기에 맞춰 @StateObject로 선언된 객체의 생명 주기를 관리합니다. 즉, 뷰가 처음 생성될 때 객체도 함께 생성되며, 뷰가 유지되는 동안에는 뷰가 여러 번 다시 그려지더라도 객체는 파괴되지 않고 상태를 그대로 유지합니다. ObservableObject 인스턴스를 처음으로 생성하는 뷰에서 사용해야 합니다.
- @ObservedObject: 객체의 관찰자(Observer)입니다. 객체를 직접 소유하지 않고, 외부에서 생성된 ObservableObject 인스턴스를 참조하여 관찰하는 역할만 합니다. 따라서 뷰가 다시 그려질 때 상위 뷰의 상태에 따라 객체가 새로 생성될 수 있어 상태 유지를 보장하지 않습니다. 다른 뷰로부터 ObservableObject 인스턴스를 전달받을 때 사용합니다.
// Source of Truth 역할을 할 데이터 모델
class UserSettings: ObservableObject {
@Published var score = 0
}
// 부모 뷰: UserSettings의 소유권을 가짐
struct GameView: View {
// @StateObject를 사용해 UserSettings 인스턴스를 생성하고 소유합니다.
// 이 뷰가 살아있는 동안 settings 객체는 단 한 번만 생성됩니다.
@StateObject private var settings = UserSettings()
var body: some View {
VStack(spacing: 20) {
Text("현재 점수: \\(settings.score)")
.font(.largeTitle)
// 자식 뷰에 settings 객체를 전달합니다.
ScoreControlView(settings: settings)
}
}
}
// 자식 뷰: UserSettings를 관찰만 함
struct ScoreControlView: View {
// @ObservedObject를 사용해 외부에서 생성된 객체를 받습니다.
// 이 뷰는 settings 객체를 소유하지 않습니다.
@ObservedObject var settings: UserSettings
var body: some View {
Button("점수 올리기") {
settings.score += 1
}
}
}
@EnvironmentObject
@EnvironmentObject는 뷰 계층 구조가 깊어질 때, 데이터를 단계별로 일일이 전달하지 않고 앱의 특정 환경(Environment) 내 모든 하위 뷰가 쉽게 접근할 수 있게 해주는 매우 편리한 SoT 관리 도구입니다.
@ObservedObject는 부모 뷰가 자식 뷰에게 직접 객체를 전달해야 하지만, @EnvironmentObject는 상위 뷰 어딘가에서 한번만 주입해주면 그 아래에 있는 어떤 뷰에서든 마치 전역 데이터처럼 꺼내 쓸 수 있습니다.
"Wi-Fi 공유기”에 비유할 수 있습니다. 집안 어딘가에 공유기(.environmentObject())를 설치해두면, 어느 방에서든 비밀번호만으로(@EnvironmentObject) Wi-Fi에 접속할 수 있는 것과 같습니다. 각 방마다 인터넷 선을 끌어올 필요가 없는 것이죠.
import SwiftUI
// 1. ObservableObject 모델은 동일하게 준비합니다.
class AppSettings: ObservableObject {
@Published var isDarkMode = false
}
// 2. 앱의 진입점에서 environmentObject를 주입합니다.
@main
struct MyApp: App {
// 앱의 생명 주기와 함께할 객체를 생성합니다.
@StateObject private var settings = AppSettings()
var body: some Scene {
WindowGroup {
ContentView()
// ContentView와 그 모든 하위 뷰에 settings 객체를 주입합니다.
.environmentObject(settings)
}
}
}
'iOS > SwiftUI' 카테고리의 다른 글
[SwiftUI] WWDC2021 - Demystify SwiftUI - Dependency (0) | 2025.09.17 |
---|---|
[SwiftUI] WWDC2021 - Demystify SwiftUI - Lifetime (0) | 2025.09.07 |
[SwiftUI] WWDC2021 - Demystify SwiftUI - Identity (0) | 2025.07.26 |
[SwiftUI] View Protocol과 ViewBuilder (0) | 2025.04.17 |
[SwiftUI] SwiftUI란 (0) | 2025.04.17 |