@Property Wrappers
property wrapper는 속성 저장 방법을 관리하는 코드와 속성을 정의하는 코드 사이에 구분 계층을 추가한다.
예를 들어, 쓰레드 안전성 검사를 제공하거나. 자신의 실제 자료를 데이터베이스에 저장하는 속성이 있다면, 모든 속성에 대해 그 코드를 작성해야 한다.
프로퍼티래퍼를 사용할 땐, 래퍼를 한 번 작성하면 여러 속성에 적용함으로써 관리 코드를 재사용한다.
프로퍼티 래퍼를 정의하기 위해 wrappedValue 프로퍼티를 정의한 구조체, 열거형 또는 클래스를 만든다.
TwelveOrLess구조체는 래핑하는 값이 항상 12와 같거나 더 작은 숫자가 저장된다는 것을 보장한다. . 더 큰 숫자를 저장하면 12를 대신 저장한다.
@propertyWrapper
struct TwelveOrLess {
private var number = 0
var wrappedValue: Int {
get { return number }
set { number = min(newValue, 12) }
}
}
setter는 새로운 값이 12보다 작다는 것을 보장하며, getter는 저장한 값을 리턴한다.
이 예제에서 number선언부는 TwelveOrLess의 구현에서만 number가 사용될 수 있도록 private로 변수를 표기한다.
다른 곳에서 작성된 코드는 wrappedValue를 위한 getter와 setter를 사용하여 값에 접근하고 직접적으로 number를 사용할 수 없다.
속성 앞에 wrapper 이름을 어트리뷰트로 작성함으로 속성에 wrapper를 적용한다.
아래의 예시는 직사각형의 변 길이가 항상 12이하가 되도록 하기 위해 @TwelveOrLess 래퍼를 사용하는 구조체이다.
struct SmallRectangle {
@TwelveOrLess var height: Int
@TwelveOrLess var width: Int
}
var rectangle = SmallRectangle()
print(rectangle.height)
// Prints "0"
rectangle.height = 10
print(rectangle.height)
// Prints "10"
rectangle.height = 24
print(rectangle.height)
// Prints "12"
@TwelveOrLess를 @어트리뷰트로 작성하는 대신 TwelveOrLess구조체 안에서 명시적으로 자신의 속성을 포장할 수 있다.
struct SmallRectangle {
private var _height = TwelveOrLess()
private var _width = TwelveOrLess()
var height: Int {
get { return _height.wrappedValue }
set { _height.wrappedValue = newValue }
}
var width: Int {
get { return _width.wrappedValue }
set { _width.wrappedValue = newValue }
}
}
Setting Initial Values for Wrapped Properties(래핑된 프로퍼티를 위한 초기값 설정)
@TwelevOrLess정의 내에서 number에 초기 값을 주는 것으로 property wrapper의 초기 값을 설정했다.
이 property wrapper를 사용하는 코드는, TwelveOrLess가 wrap한 속성에 다른 초기 값을 지정할 수 없다.
초기 값 설정 및 다른 사용자 정의?? ==> 쉽게 말해 0이 아닌(number에서)다른 초기값을 설정하거나 커스텀 정의를 지원하려면 속성 wrapper에 이니셜라이저를 추가할 필요가 있다.
다음은 SmallNumber이라는 TwelveOrLess의 기능을 늘린 버전인데, 이니셜라이저를 정의하여 포장 값과 최대 값을 설정한다.
@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)
}
}
init(), init(wrappedValue:), init(wrappedValue:maximum:)이라는 세 개의 이니셜라이저를 포함하며 아래 예제에서 wrappedValue, maximum을 설정할 때 이를 사용한다.
init()
속성에 wrap를 적용하여 이니셜라이저를 지정하지 않으면 Swift가 init() 기본 이니셜라이저를 선택한다.
struct ZeroRectangle {
@SmallNumber var height: Int
@SmallNumber var width: Int
}
var zeroRectangle = ZeroRectangle()
print(zeroRectangle.height, zeroRectangle.width)
// Prints "0 0"
init(wrappedValue:)
속성에 초기값을 지정하면 Swift가 init(wrappedValue:) 이니셜라이저를 선택한다.
struct UnitRectangle {
@SmallNumber var height: Int = 1
@SmallNumber var width: Int = 1
}
var unitRectangle = UnitRectangle()
print(unitRectangle.height, unitRectangle.width)
// Prints "1 1"
wrap속성에 =1를 작성하면 init(wrappedValue:) 이니셜라이저를 호출한다. Smallnumber로 포장한 height와 width 인스턴스는 SmallNumber(wrappedValue: 1)호출로 생성된다. 이니셜라이저는 위에서 지정한 12라는 기본 최대값을 사용한다.
init(wrappedValue:maximum:)
초기값과 최대값을 제공하는경우? ==> 설정한 이니셜라이저에 맞는 매개변수를 전달하는 경우 개수와 형태에 맞는 이니셜라이저를 호출한다.
struct NarrowRectangle {
@SmallNumber(wrappedValue: 2, maximum: 5) var height: Int
@SmallNumber(wrappedValue: 3, maximum: 4) var width: Int
}
var narrowRectangle = NarrowRectangle()
print(narrowRectangle.height, narrowRectangle.width)
// Prints "2 3"
narrowRectangle.height = 100
narrowRectangle.width = 100
print(narrowRectangle.height, narrowRectangle.width)
// Prints "5 4"
SmallNumber로 포장한 height와 width 인스턴스는 위에서 지정한 이니셜라이저에 맞게 호출된다.
개별설정
property wrapper에 파라미터를 포함시켜서, 초기 상태를 설정하거나 다른 옵션을 전달할 수 있다.
아래 구문이 property wrapper를 사용하는 가장 일반적인 방식이다. 무슨 인자든 필요하면 attribute로 제공할 수 있으며 이를 이니셜라이저로 전달한다.
struct MixedRectangle {
@SmallNumber var height: Int = 1
@SmallNumber(maximum: 9) var width: Int = 2
}
var mixedRectangle = MixedRectangle()
print(mixedRectangle.height)
// Prints "1"
mixedRectangle.height = 20
print(mixedRectangle.height)
// Prints "12"
height 를 래핑한 SmallNumber 의 인스턴스는 기본 최대값은 12를 사용하는 SmallNumber(wrappedValue: 1) 을 호출하여 된다. width 를 래핑한 이 인스턴스는 SmallNumber(wrappedValue: 2, maximum: 9) 를 호출하여 생성된다.
포장된 속성에 있는 값 꺼내기?(Projection a Value From a Property Wrapper)
wrapper 값에 더하여 property wrapper는 ppojected value를 정의함으로써 추가 기능을 할 수 있다.
예를 들어 데이터베이스 접근을 관리하는 property wrapper는 자신의 projected value에 대하여 flushDataConnection()을 나타낸다. projected value의 이름은 달러기호($)로 시작하는 것만 제외하면 wrapper값과 똑같다.
코드에서 $로 시작하는 속성을 정의할 순 없기 때문에 projected value가 자신이 정의한 속성을 간솝할 일은 없다.
위의 SmallNumber 예제에서 큰 수를 속성에 설정하려고 하면 property wrapper는 저장 전에 수치를 적당히 조정한다.
아래 코드는 Small Number 구조체에 projectedValue속성을 추가하여 새 값을 속성에 저장하기 전에 속성 포장이 새 값을 적당히 조정했는지 추가한다.
@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"
someStructure.$someNumber라고 작성하면 wrapper가 projectedValue에 접근한다.
4처럼 12보다작은수를 저장하면 someStructure.$someNumber는 false이다. 하지만 55처럼 큰 수를 저장하면 true이다.
properties wrapper는 어떤 타입의 값이든 자신이 내민 값으로 반환할 수 있다. 이 예제에선 properties wrapper가 수치를 적당히 조정 했는지에대한 정보를 나타내므로 Bool값을 자신의 projected value로 설정했다.
더 많은 정보를 나타내야 하면 wrapper는 다른 자료 타입 인스턴스를 반환하거나, wrapper의 인스턴스를 자신의 projectedValue로 드러내기 위해 self를 리턴할 수 있다.
setter나 인스턴스 메서드 같이, 타입의 일부분인 코드에서 projected value값에 접근할 땐 다른 속성에 접근할 때 처럼 속성 이름 앞의 self를 생략할 수 있다.
다음 예제는 height와 width wrapper의 projected value를 $height, $width로 참조한다.
enum Size {
case small, large
}
struct SizedRectangle {
@SmallNumber var height: Int
@SmallNumber var width: Int
mutating func resize(to size: Size) -> Bool {
switch size {
case .small:
height = 10
width = 20
case .large:
height = 100
width = 100
}
return $height || $width
}
properties wrapper 구문은 getter와 setter가 있는, 속성을 위한 구문일 뿐이기 때문에 height와 width로의 접근은 다른 어떤 속성으로의 접근과 똑같이 동작한다.
⭐️⭐️⭐️
예를 들어 resize(to:) 코드는 자신의 propertis wrapper를써서? height와 width에 접근한다.
resize(to: large)를 호출하면 large라는 switch문이 case절이 직사각형 height와 width를 100으로 설정한다. wrapper는 그 속성 값이 12보다 커지는 것을 막고. 자신이 값을 조정한 사실을 기록하기 위해 projected value를 true로 설정한다.
resize(to:) 끝에서 리턴문이 $height, $width를 검사하여 properties wrapper가 height나 width중 어느 하나를 조절했는지를 리턴한다.