구조체는 기본적으로 내부에서 값을 변경할 수 없다. Struct로 뷰를 그리는 SwiftUI특성상 원칙대로로는 뷰에서 값을 변경할 수 없다는 의미이다.
mutating 키워드를 이용하면 되지 않냐 싶지만, 이마저도 어림도 없다.
왜 구조체에선 값을 변경할 수 없을까?
class는 레퍼런스 타입으로서, 인스턴스를 생성하면 값이 아닌 주소를 갖고 있는다. 내부의 값이 아무리 변경되어도 메모리 주소는 변하지 않아 불변성을 가진다.
하지만, Struct는 값타입이다.
내부의 값이 바뀌면 새로운 메모리를 할당해 새로운 인스턴스를 만들어낸다. 심지어 Struct는 cow도 구현되어 있지 않으니,,, SwiftUI입장에서 뷰를 새로 그릴때마다 인스턴스를 새로 만든다는건, 아주 센세이션한 이상한 짓이라는걸 알 수 있다.
앞서 언급했듯 SwiftUI View에선 mutating 키워드도 사용할 수 없다. 그럼 이제 뷰에서 값을 변경할 수 있는 방법은 없는것인가?
struct ContentView: View {
@State private var test = "1"
var body: some View {
VStack {
Button(test) {
test += "123"
}
}
.padding()
}
}
@State 키워드를 사용하면 값을 변경할 수 있을 뿐더러 뷰가 리프레시 되어 다시 그려진다! State가 무엇이길래 값을 변경할 수 있는지, 뷰가 리프레시 되는지 알아보도 하자.
일단 커스텀 프로퍼티 래퍼를 만들어보았다
struct ContentView: View {
@DateWrapper var aaa: Date
var body: some View {
VStack {
Text("\(aaa)")
Text($aaa)
}
.padding()
}
}
@propertyWrapper
struct DateWrapper: DynamicProperty {
private var value: Date
var projectedValue: String {
let format = DateFormatter()
format.dateFormat = "yyyy"
return format.string(from: value)
}
init() {
self.value = Date()
}
var wrappedValue: Date {
get { self.value }
set { self.value = newValue }
}
}
projectedValue는 @propertyWrapper의 프로퍼티인 wrappedValue말고 부가적인 프로퍼티를 정의하여 사용할 수 있게 하기 위해 만들어졌고, $으로 접근 할 수 있다.
이런식으로 꼭 wrappedValue와 projectedValue의 타입이 같지 않아도 된다.
@propertyWrapper
struct CustomState: DynamicProperty {
private var value: String
var projectedValue: String = "Value의 주소"
init() {
self.value = "value의 값"
}
var wrappedValue: String {
get { self.value }
set { self.value = newValue }
}
func update() {
print("뷰가 최신인지 확인 후 리프레시")
}
}
아마 State가 이런식으로 구현되어 있지 않을까? 싶어 만들어보았다.
update function은 DynamicProperty에서 업데이트 될때마다 실행되는 함수다. 실제로 DynamicProperty에 찍어 들어가보면,
SwiftUI calls this function before rendering a view's
View/body-swift.property
to ensure the view has the most recent value.SwiftUI는 뷰의 View/body-swift.property를 렌더링하기 전에 이 함수를 호출하여 뷰가 가장 최신의 값을 가지고 있는지 확인합니다.
이런식으로 이야기 하고 있다는걸 알 수 있다.