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