[회고] 신입 iOS 개발자가 되기까지 feat. 카카오 자세히보기

🍎 Apple/Swift

[Swift] Property Wrapper

inu 2022. 2. 24. 15:55

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] 문자열 처리  (1) 2022.01.22
[Swift] NSCache와 NSDictionary의 차이점  (0) 2022.01.22