티스토리 뷰

728x90

Property Wrappers

 

swift 5.1부터 추가된 기능 입니다.

Property Wrapper는 프로퍼티가 저장되는 코드 프로퍼티가 정의되는 코드를 분리하는 계층을 추가합니다.

 

정의

 

Property Wrapper를 정의 하기위해, wrappedValue 키워드의 프로퍼티가 정의되어 있는 구조체(struct), 열거형(enum), 클래스(class)를 만듭니다.

 

예제

아래는 구조체 TwelveOrLess에 감싸지는 값이 항상 12 이하라는걸 보장합니다.

만약 저장하는 값이 12보다 높을경우 12를 저장합니다.

 

@propertyWrapper
struct TwelveOrLess {
    private var number = 0   // number는 TwelveOrLess 내부에서만 사용될거기 때문에 private을 사용
    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, 12) }  // min(값, 최대값) 최대값 아래의 숫자만 저장
    }
}

 

setter는 새로운 값이 12 이하라는걸 보장하며, 

getter는 저장된 값을 반환 합니다. 

 

 

프로퍼티에 wrapper를 적용하려면  프로퍼티 앞 wrapper의 이름에 attribute (@)를 적어줍니다.

 

struct SmallRectangle {
    @TwelveOrLess var height: Int
    @TwelveOrLess var width: Int
}

var rectangle = SmallRectangle()
// 1
print(rectangle.height)
// Prints "0"

rectangle.height = 10
// 2번
print(rectangle.height)
// Prints "10"

rectangle.height = 24   
// 3번
print(rectangle.height)
// Prints "12"

 

height와 width 프로퍼티는 TwelveOrLess의 number로 부터 초기 값 0 을 가지기 때문에 

1번에서 출력하는 값은 0 이 나오고,

2번에서 rectangle.height 값을 10으로 저장 했을 때 12 이하의 숫자 이기 때문에 12가 출력이 되었고,

3번에서 rectangle.height 값을 24로 저장 했을 땐 12를 초과하는 수이기 때문에 

set { number = min(newValue, 12) }

이 부분에서 걸려 12로 반환이 됩니다. 

 

 

프로퍼티에 wrapper를 적용하면 컴파일러 wrapper를 저장하는 코드 wrapper를 통해 프로퍼티에 접근하는 코드를 합성합니다.

 

조금전 @ 키워드를 이용해서 wrapper를 사용하는 방법 외에도

@TwelveOrLess var height: Int

 

내부 프로퍼티들로 TweleveOrLess 구조체를 명시적으로 사용할 수 있습니다. 

 

//@propertyWrapper
//struct TwelveOrLess {
//    private var number = 0
//    var wrappedValue: Int {
//        get { return number }
//        set { number = min(newValue, 12) }
//    }
//}

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 }
    }
}

 

_height _width 프로퍼티는 property wrapper TwelveOrLess 의 인스턴스를 가집니다.

getter와 setter 에서 wrappedValue 프로퍼티에 접근 합니다.

 

 

래핑된 프로퍼티에 초기값 세팅 (Setting Initial Values for Wrapped Properties )

 

위 예제에서는 래핑된 프로퍼티의 초기값이 TwelveOrLess에 정의된 number의 초기값으로 설정되었습니다.

위 예제중 SmallRectangle 의 정의는 height/ width 의 초기값을 제공할 수 없습니다. 

초기값을 제공하기 위해서, property wrapper는 이니셜라이저를 추가해야 합니다.

아래 예제에 TwelveOrLess의 확장된 버전인 SmallNumber를 보면 wrappedValue maximum의 이니셜라이저가 정의 되어있습니다. 

 

@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)
    }
}

 

구조체 SmallNumber 에는 세 개의 이니셜라이저가 정의 되어 있습니다. 

init(), init(wrappedValue:), init(wrappedValue:maximum:) 

wrappedValue와 maximum값을 세팅하기 위해 사용합니다.

 

프로퍼티에 wrapper를 적용하고 초기값을 명시하지 않았을때, 

Swift는 자동으로 init() 이니셜라이저를 사용하여 wrapper를 세팅합니다. (아래 예시 참고)

 

struct ZeroRectangle {
    @SmallNumber var height: Int
    @SmallNumber var width: Int
}

var zeroRectangle = ZeroRectangle()
print(zeroRectangle.height, zeroRectangle.width) // "0 0"

 

@SmallNumber를 선언해줌으로써, height/ width 값에 SmallNumber에 있는 초기값들이 세팅 되며 (number = 0, maximum = 12) 

위처럼 아무값도 세팅해두지 않았기 때문에 zeroRectangle.width, zeroRectangle.height 값이 각각 0으로 출력이 됩니다. 

 

 

프로퍼티 초기값(wrappedValue)를 따로 명시하는 경우엔 init(wrappedValue:) 이니셜라이저가 사용됩니다. 

 

struct UnitRectangle {
    @SmallNumber var height: Int = 1  // init(wrappedValue:) 호출
    @SmallNumber var width: Int = 1   // init(wrappedValue:) 호출
}

var unitRectangle = UnitRectangle()
print(unitRectangle.height, unitRectangle.width) // "1 1"

 

@SmallNumber 부분에서 초기 값을 1로 설정해주게 되면,

SmallNumber 내부의 코드에서 아래의 init(wrappedValue:) 이니셜라이저가 호출됩니다.

    init(wrappedValue: Int) {
        maximum = 12
        number = min(wrappedValue, maximum) 
    }

UnitRectangle에서 height/ width값을 각각 1로 설정해 두었으니 wrappedValue는 1, maximum은 그대로 12 가 됩니다.

 

 

프로퍼티 선언시 @PropertyWrapper(wrappedValue:, maximum:) 이렇게 인자들을 넘겨줄 수도 있습니다.

이때 @SmallNumber(wrappedValue:, maximum:) 를 호출하면, init(wrappedValue, maximum) 이니셜라이저가 호출되고,

@SmallNumber(wrappedValue:) 를 호출하면 init(wrappedValue) 이니셜라이저가 호출됩니다. (아래 사진, 예제 참고)

해당 PropertyWrapper 선언후 괄호를 열면 내부에 있는 이니셜라이저들이 보여짐

 

struct NarrowRectangle {
    @SmallNumber(wrappedValue: 2, maximum: 5) var height: Int  // 초기값 2, maximum 5
    @SmallNumber(wrappedValue: 3, maximum: 4) var width: Int   // 초기값 3, maximum 4
}

var narrowRectangle = NarrowRectangle()
// 1 
print(narrowRectangle.height, narrowRectangle.width) // "2 3"

// 2
narrowRectangle.height = 100
narrowRectangle.width  = 100
print(narrowRectangle.height, narrowRectangle.width) // "5 4"

 

위 문법은 property wrapper를 사용하는 가장 제너럴한 방법 입니다. 

 

height의 wrappedValue를 2로, maximum을 5로 지정하고

width의 wrappedValue를 3으로, maximum을 4로 지정해서 

첫번째 print는 height/ width 값이 각각 초기값을 출력 "2, 3"

두번째 print는 height/ width 값에 100을 추가히지만 maxmum 값보다 높으므로 maximum값을 출력 "5 4"

 

 

Property wrapper에 인자를 포함하면서, 초기값을 할당 하는 방법도 있습니다.

 

struct MixedRectangle {
    @SmallNumber var height: Int = 1
    @SmallNumber(maximum: 9) var width: Int = 2
}

var mixedRectangle = MixedRectangle()
print(mixedRectangle.height, mixedRectangle.width) // "1 2"

mixedRectangle.height = 20
mixedRectangle.width = 10
print(mixedRectangle.height, mixedRectangle.width)  // "12 9"

 

위 예제에서 height에는 init(wrappedValue:1) 의 이니셜라이저가 호출되고,

width는 init(wrappedValue:2, maximum:9) 의 이니셜라이저가 호출됩니다.

 

 

Projecting a Value From a Property Wrapper

 

wrappedValue외에도 projectedValue를 정의해서 추가 기능을 사용할 수 있습니다.

projectedValue 에는 프로퍼티 이름앞에 $ 기호로 접근할 수 있습니다. 

 

 SmallNumber 예제에서 프로퍼티에 너무 큰 숫자로 세팅려고 하면 property wrapper에서 

저장하기 전에 숫자를 조정 합니다. (maximum 값보다 클 경우 maximum값으로 저장)

 

아래 코드는 ProjectedValue 프로퍼티를 SmallNumber에 추가하여 새 값을 저장하기 전에 

프로퍼티의 새 값을 조정했는지의 여부를 추적합니다.

 

@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) // "false"

someStructure.someNumber = 55
print(someStructure.$someNumber) // "true"

someStructure.$someNumber는 wrapper의 projectedValue에 접근합니다.

할당되는값 (newValue)이 12 이상일 경우 projectedValue는 true가 되고,

12 미만일 경우 projectedValue는 false가 됩니다. 

 

projectedValue는 Bool 외에도 어떤 타입이라도 될 수 있습니다. 

더 많은 정보를 노출해야하는 wrapper인 경우, 다른 데이터 타입의 인스턴스를 반환하거나, self를 반환할 수도 있습니다. 

 

728x90