DynamicPropertyPermalink

DynamicProperty는 내부적으로 @State, @AppStorage, @StateObject, @Environment 가 SwiftUI 에서 동작하게 해주는 마법같은 프로토콜이다.

공식 설명은 다음과 같다.

An interface for a stored variable that updates an external property of a view.

View외부에 View가 읽거나 쓰는 변수를 저장해두는 기능을 추가해주는 역할을 한다.

가령 Viewstruct이며 immutable이고 SwiftUI 렌더링 엔진이 수시로 파괴하고 새롭게 생성하지만, 그 내부에 있는 @State같은 값들은 계속해서 동일한 것이 유지되어야 하기 때문에 이러한 방식을 취한다.

이러한 SwiftUI의 동작방식 때문에 Viewinit에서 myState = .init(initialValue: ...) 처럼 @State를 초기화 하는 방식은 위험한 방식이다.

이 부분에 대해서 더 자세히 알고 싶다면 How to (not) initialize @State inside the View’s init 이라는 글을 읽어보는 것을 추천한다.

어쨌든 DynamicProperty의 사용법을 알아보자. 우리도 이 프로토콜을 conform 하는 property wrapper를 이용하면 @State 처럼 동작하는 property wrapper를 만들 수 있다.

DynamicProperty 사용법Permalink

@propertyWrapper
struct MyAppStorage: DynamicProperty {
    private let storageKey: String
    @State private var value: Bool
    
    init(_ storageKey: String) {
        self.storageKey = storageKey
        self.value = UserDefaults.standard.bool(forKey: storageKey)
    }
    
    var wrappedValue: Bool {
        get {
            value
        }
        nonmutating set {
            UserDefaults.standard.set(newValue, forKey: storageKey)
            value = newValue
        }
    }
}

단순하게 BoolUserDefaults에 저장할 수 있는 propertyWrapper이다.

@propertyWrapper 이므로 wrappedValue를 적절하게 구현을 해주어야 한다.

setnonmutating을 붙여주는 이유는 struct의 객체를 변경하지 않고 사용하기 위함이며 내부의 value값은 변하더라도 struct의 값이 아닌 SwiftUI 가 외부적으로 관리해주는 값이기 때문에 변경해도 문제가 없다는 것을 알려주기 위함이다.

Viewbody같은 컨텍스트는 struct이기 때문에 mutating context가 아니다. 따라서 mutating set으로 표기되면 사용할 수 없기 때문에 SwiftUI에서 사용하는 DynamicProperty 들의 wrappedValue setter들은 nonmutating을 표기하는 것이다.

사용법은 다음과 같다.

struct ContentView: View {
    @MyAppStorage("myKey")
    private var isTapped: Bool
    
    var body: some View {
        VStack {
            Text("Hello, world! \(isTapped)")
            Button("Tap") {
                isTapped.toggle()
            }
            .padding(.top)
        }
        .padding()
    }
}

Categories:

Updated:

Comments