[SwiftUI] Source of Truth와 프로퍼티 래퍼 이해하기

·2024년 4월 18일

SwiftUI

목록 보기
1/1

Source of Truth가 뭘까 . .

UIKit과 다르게 SwiftUI는 UI의 상태가 데이터의 상태에 따라 결정된다.
'Source of Truth'라는 용어는, 앱의 특정 데이터에 대한 단 하나의 신뢰할 수 있는 원천을 의미한다. 데이터의 변경이 있을 때 해당 데이터를 참조하는 모든 뷰들이 자동으로 업데이트되어야 함을 보장하는 것.

예를 들어, 어떤 앱에서 사용자의 이름을 저장하고 있다고 치자. SwiftUI에서는 사용자의 이름을 저장하는 변수가 'Source of Truth'가 된다. 이 변수에 저장된 이름은 여러 뷰에서 사용될 수 있지만, 이름 데이터는 한 군데에서만 관리되어야 한다. 이렇게 하면 데이터 관리가 중복되거나 오류가 생길 가능성을 줄일 수 있다.

그리고 반드시 단일 'Source of Truth'가 존재해야 한다. 단일 'Source of Truth'가 존재해야 한다는 것의 의미는, 어떤 특정 데이터에 대해 하나의 정확하고 신뢰할 수 있는 출처가 있어야 한다는 의미. 이렇게 하면 데이터의 무결성을 유지할 수 있고, 앱 내에서 데이터가 변경될 때 그 변경이 예측 가능하고 관리하기 쉬워진다.

View에서 가져야 할 Source of Truth는 @State를 통해 활용할 수 있다. @State는 SwiftUI의 여러 Property Wrapper 중 하나인데, @State의 개념을 살펴보기 전에 먼저 Property Wrapper가 뭔지 살펴보자.



Property Wrapper

프로퍼티란?

먼저, 프로퍼티는 클래스, 구조체 또는 열거형 등에 속한 값이다. Swift에서는 주로 두 가지 타입의 프로퍼티를 사용한다.

  • 저장 프로퍼티(Stored Properties): 실제 값을 인스턴스에 저장하는 프로퍼티입니다. 클래스와 구조체에서 사용.
  • 계산 프로퍼티(Computed Properties): 값을 직접 저장하지 않고, 계산을 통해 값을 제공하는 프로퍼티입니다. 클래스, 구조체, 열거형에서 사용된다.

Property Wrapper란?

SwiftUI에서의 상태 관리는 UI가 데이터의 변경에 반응하여 자동으로 업데이트되도록 설계되어 있다. 이를 위해 SwiftUI는 여러 상태 프로퍼티 래퍼들을 제공한다. 여기서 프로퍼티 래퍼들은 데이터의 저장 및 변화 관리를 용이하게 하며, 뷰의 재생성과 직접적으로 관련이 있다.

Property Wrapper 예제

@propertyWrapper
struct Logged<T> {
    private var value: T
    var wrappedValue: T {
        get { value }
        set {
            print("Setting value to \(newValue)")
            value = newValue
        }
    }
    
    init(wrappedValue initialValue: T) {
        self.value = initialValue
        print("Initialized with \(initialValue)")
    }
}

struct Example {
    @Logged var count: Int
}


@State와 @Binding

@State

@State는 위에서 언급했던 프로퍼티 래퍼 중 하나이다. View에서 가져야 할 Source of Truth라고 할 수 있다. Data에 대한 상태를 저장하고 관찰한다.
View에 대한 상태를 저장하기 위한 목적으로 설계되었기 때문에, 해당 뷰가 data를 소유하고 관리한다는 개념을 명시적으로 나타내기 위해 private 접근 레벨을 사용하는 게 좋다.
Data가 변경되면 View도 함께 변경된다. 즉, view가 data에 의존성을 갖고 있다는 것을 의미한다. 변경사항이 감지될 때마다 매번 View를 재생성한다. 뷰의 재생성은 실제로 전체 뷰가 새로 그려지는 것을 의미하지 않고, SwiftUI의 뷰 렌더링 시스템이 변경된 상태에 대해 반응하여 뷰의 일부분만을 업데이트한다.

@Binding

  • 외부에서 Source of Truth를 주입받고 참조 받는다.
  • 상위 뷰가 가진 상태를 하위 뷰에서 사용하고 수정할 수 있도록 한다.
  • 값을 읽고 수정하여 다른 뷰에 갱신된 데이터를 전달하는 역할을 한다.
  • 다른 뷰에서는 $ 접두어를 사용해야 하고 같은 뷰에서는 $접두어를 쓰지 않아도 된다.

@State와 Binding 활용 예시

import SwiftUI

// ParentView는 @State를 사용하여 슬라이더 값 관리
struct ParentView: View {
    @State private var sliderValue: Double = 0 // Source of Truth

    var body: some View {
        VStack {
            Text("Current Value: \(sliderValue, specifier: "%.1f")")
            ChildView(sliderValue: $sliderValue)
        }
        .padding()
    }
}

// ChildView는 ParentView에서 제공하는 @Binding을 통해 슬라이더 값에 대한 바인딩을 가짐
struct ChildView: View {
    @Binding var sliderValue: Double // Source of Truth를 참조

    var body: some View {
        Slider(value: $sliderValue, in: 0...100)
            .padding()
    }
}

struct ContentView2: View {
    var body: some View {
        ParentView()
    }
}


#Preview {
    ContentView2()
}


결론

SwiftUI처럼 이렇게 중앙에서 데이터를 관리하는 것은 특히 큰 프로젝트나 여러 데이터가 상호작용하는 복잡한 상황에서 매우 유용하다. 데이터의 출처가 명확하고 하나이기 때문에 버그를 찾기 쉽고, 데이터를 업데이트하거나 수정할 때 다른 부분에 미치는 영향을 쉽게 예측할 수 있다.

0개의 댓글