@State, @StateObject, @AppStorage… 등
SwiftUI를 공부했다면 익숙할 듯
위에 언급된 애들은 전부 property wrapper
요 글에선 얘네가 정확히 뭐고, 어떤 역할을 하는지에 대해 알아보려고 한다.
일단 이름 그대로 프로퍼티를 감싸는 것이라고도 생각할 수 있을 것 같다.
공식 문서에 의하면,
→ 관리 코드를 한번만 작성하고 여러 프로퍼티에 적용할 수 있으므로 재사용성이 아~주 높아진다.
@propertyWrapper
struct SampleStruct {
}
@propertyWrapper
wrappedValue
프로퍼티 정의@propertyWrapper
struct SmallNumber {
private var maximum: Int
private var number: Int
var wrappedValue: Int {
get { return number }
set { number = min(newValue, maximum) }
}
}
struct ZeroRectangle {
@SmallNumber var height: Int
@SmallNumber var width: Int
}
SmallNumber
의 wrappedValue
ZeroRectangle
의 height
, width
모두 SmallNumber
의 로직처럼 maximum을 넘는지 검사하고 싶을 때, property wrapper로 정의해 두지 않았다면 중복되는 로직의 코드를 2번 작성해 줘야 함위 코드로 계속 진행해 보겠슴
https://zeddios.tistory.com/1221
요기는 제드님 블로그를 보고 이해했습니다 감사합니다(—)(__)
나는 구조체니까 당연히 멤버와이즈로 초기화할 수 있을 테고, init이 따로 필요없을 거라 생각해서
let zeroRectangle = ZeroRectangle(height: 3, width: 3)
이렇게 선언해 주었더니
인스턴스를 생성하려고 할 때 이렇게 떴었음.
init(wrappedValue: Int) {
maximum = 12
number = min(wrappedValue, maximum)
}
wrappedValue
를 초기화해 주는 이니셜라이저를 이렇게 넣었더니 Int
형으로 잘 나옴.
물론 maximum
프로퍼티도 초기화해줄 수 있고
init()
, init(wrappedValue:maximum:)
도 모두 가능함.
init(wrappedValue: Int, maximum: Int) {
self.maximum = maximum
number = min(wrappedValue, maximum)
}
init() {
maximum = 12
number = 3
}
let zeroRectangle = ZeroRectangle()
print(zeroRectangle.height, zeroRectangle.width) // 3 3
init(wrappedValue: Int) {
maximum = 12
number = min(wrappedValue, maximum)
}
struct ZeroRectangle {
@SmallNumber var height: Int = 3
@SmallNumber var width: Int
}
let zeroRectangle = ZeroRectangle(width: 15)
print(zeroRectangle.height, zeroRectangle.width) // 3 12
init(wrappedValue: Int, maximum: Int) {
self.maximum = maximum
number = min(wrappedValue, maximum)
}
let zeroRectangle = ZeroRectangle(height: .init(wrappedValue: 3, maximum: 2),
width: .init(wrappedValue: 12, maximum: 15))
print(zeroRectangle.height, zeroRectangle.width) // 2 12
이런 식으로 초기화가 가능하긴 하지만,
보통 쓰는 건 property wrapper에 인수를 포함하여 초기값을 설정하는 것
@propertyWrapper
struct SmallNumber {
private var maximum: Int
private var number: Int
var wrappedValue: Int {
get { return number }
set { number = min(newValue, maximum) }
}
init() {
maximum = 12
number = 0
}
init(wrappedValue: Int) {
maximum = 12
number = min(wrappedValue, maximum)
}
init(wrappedValue: Int, maximum: Int) {
self.maximum = maximum
number = min(wrappedValue, maximum)
}
}
struct ZeroRectangle {
@SmallNumber(wrappedValue: 3) var height: Int // 📌
@SmallNumber(wrappedValue: 5, maximum: 13) var width: Int // 📌
}
$
를 사용하여 접근@propertyWrapper
struct SmallNumber {
private var number: Int
private(set) var projectedValue: Bool
var wrappedValue: Int {
get { return number }
set {
if newValue > 12 {
number = 12
projectedValue = true
} else {
number = newValue
projectedValue = false
}
}
}
init() {
self.number = 0
self.projectedValue = false
}
}
struct SomeStructure {
@SmallNumber var someNumber: Int
}
var someStructure = SomeStructure()
someStructure.someNumber = 4
print(someStructure.$someNumber)
// Prints "false"
someStructure.someNumber = 55
print(someStructure.$someNumber)
// Prints "true"
요거는 공식 문서에 적혀 있었던 예시인데
someNumber
이 maximum을 넘어서 조정되었는지 아닌지 추가적인 정보를 제공해 주는 projectedValue
뭔가 projectedValue
라는 게 왜 꼭 굳이 필요하지? 라는 고민을 계속했었는데…
조금 더 기초적으로 접근해 보자면…
위의 코드에서 추가로projectedValue
가 아니라 일반적인 isOver
뭐 이런 변수로 지정했을 때,
// property wrapper 정의하는 부분
private(set) isOver: Bool
var wrappedValue: Int {
get { return number }
set {
if newValue > 12 {
number = 12
isOver = true
} else {
number = newValue
isOver = false
}
}
}
// someStructure 선언 이후
print(someStructure.isOver) // error
애초에 someNumber
에서 isOver
같은 프로퍼티는 접근할 수가 없으니,
특별하게 추가적인 정보를 제공할 수 있도록 만들어진 projectedValue
로 이리저리 필요한 로직을 구현하는 것
$
를 붙여서 불러오기만 하면 이 프로퍼티에 대해서 지정해 놓은 정보를 다양하게 가져올 수 있게 된다.
특히 스유를 공부하면서 정말 많은 property wrapper를 사용했는데,
내가 커스텀해서 사용할 수 있다는 게 무척이나 신기했다!
https://bbiguduk.gitbook.io/swift/language-guide-1/properties
https://zeddios.tistory.com/1221