Property Wrapper
Property Wrapper는 반복적으로 필요로하는 property 구현 패턴(getter,setter)에 대한 집합을 컴파일러에 하드코딩해놓고, 이를 라이브러리로 정의할 수 있는 일반적인 메커니즘을 제공하는 기능입니다.
Property wrapper는 프로퍼티가 저장되는 방식을 관리하는 코드와 프로퍼티를 정의하는 코드 사이에 새로운 분리 계층을 추가합니다. 개발을 진행하다보면 Thread-safe한 프로퍼티 또는 값을 DB에 저장하는 프로퍼티가 여러개 필요한 경우가 있을 것입니다. 이를 위해서는 각각의 프로퍼티 모두에 해당 처리들을 위한 코드를 작성해야 했습니다. Property wrapper를 사용하게되면 이런 값 저장을 관리하는 코드들을 Property wrapper 정의부에서 한번만 작성하면 됩니다. 그 후에는 필요한 프로퍼티에 Property wrapper를 가져다 적용하기만 하면 됩니다.
Property wrapper를 정의하려면 wrappedValue 프로퍼티가 정의된 struct, enum, class를 만들고 @propertyWrapper
를 붙여주면 됩니다. 아래 코드에서는 wrappedValue가 항상 12이하임을 보장하도록 합니다. 12보다 큰 값을 정의하려고 할 경우 12를 저장합니다.
@propertyWrapper
struct TwelveOrLess {
private var number = 0
var wrappedValue: Int {
get { return number }
set { number = min(newValue, 12) }
}
}
이렇게 만들어진 Property wrapper는 프로퍼티앞에 "@"키워드와 함께 붙여 적용할 수 있습니다. (@wrapper이름
)
아래는 적용 예시입니다.
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"
프로퍼티에 wrapper를 적용하면 컴파일러는 wrapper 자체를 저장하는 코드와 wrapper를 통해 프로퍼티에 접근하는 코드를 자동으로 합쳐줍니다. (실제로 wrapping된 값 자체를 저장하는 것은 wrapper에서 담당하고 있기 때문에 따로 만들어줄 필요가 없습니다.)
@wrapper이름
을 사용하지 않고 wrapper 객체를 활용하면 아래 코드처럼 wrapper 자체를 저장하는 코드와 wrapper를 통해 프로퍼티에 접근하는 코드가 필요해집니다.
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 }
}
}
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)
}
}
init()
, init(wrappedValue: Int)
, init(wrappedValue: Int, maximum: Int)
세가지의 이니셜라이저를 보유하고 있습니다.
struct ZeroRectangle {
@SmallNumber var height: Int
@SmallNumber var width: Int
}
var zeroRectangle = ZeroRectangle()
print(zeroRectangle.height, zeroRectangle.width)
// Prints "0 0"
초기값을 정의하지 않으면 가장 기본적인 이니셜라이저인 init()
을 사용해 wrapper를 설정합니다.
struct UnitRectangle {
@SmallNumber var height: Int = 1
@SmallNumber var width: Int = 1
}
var unitRectangle = UnitRectangle()
print(unitRectangle.height, unitRectangle.width)
// Prints "1 1"
초기값을 사용하면 init(wrappedValue: Int)
를 사용합니다.
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"
프로퍼티를 선언하면서 wrapper에 괄호를 열어 인자들을 넘기면 그에 부합하는 이니셜라이저 init(wrappedValue: Int, maximum: Int)
를 사용합니다.
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"
초기값을 주면서 인자를 넘겨줬을 경우에도 init(wrappedValue: Int, maximum: Int)
호출이 가능합니다. 할당되는 초기값은 wrappedValue로 취급됩니다.
Property wrapper는 wrappedValue 외에도 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"
참고 : https://docs.swift.org/swift-book/LanguageGuide/Properties.html#ID617
'🍎 Apple > Swift' 카테고리의 다른 글
[Swift] KVC(Key-Value Coding), KVO(Key-Value Observing) (0) | 2022.03.01 |
---|---|
[Swift] Monad (0) | 2022.03.01 |
[Swift] Copy on write (2) | 2022.01.26 |
[Swift] 문자열 처리 (0) | 2022.01.22 |
[Swift] NSCache와 NSDictionary의 차이점 (0) | 2022.01.22 |