[Swift] Property wrapper
허허.. 오랜만에 블로그 글을 쓰러 왔다...
최근에 회고도.. 잘 안썼는디.. 회고를 일주일 단위로 하려니 뭔가 눈에띄는 성과가 없는듯 하여.. 스프린트 단위로 회고를 하면 좋을 것 같다는 생각으로 안하게된... (맞습니다 이건 그냥 핑계일뿐.. 그냥 미룬겁니다 ^_^)
무튼 오늘 써볼 이야기는 Property wrapper에 관한 이야기이다.
장대한건 아니고 멤버쉽에 들어와 동료분들이 Property wrapper를 사용하시는걸 보고 나도 사용해보고싶다는 생각으로 찾아보게 되었다.
Property wrapper는 Swift5.1에서 처음 나온 문법(?)이다.
이걸 사용하면 프로퍼티에서 get{}
, set{}
설정하는 부분을 struct
혹은 class
로 만들어두고 프로퍼티의 속성을 미리 정해둘 수 있기 때문에 로직이 겹치는 보일러 플레이트 코드를 획기적으로 줄일 수 있다고한다 !
예제를 통해서 사용 방법을 알아보자 !
사용 방법
내가 높이와 너비가 각각 10 이내인 사각형 구조체를 만든다고 해보자. 그럼 아래와 같은 코드가 만들어진다.
struct Rectangle {
var width: Int {
get { self.width }
set { self.width = min(newValue, 10) }
}
var height: Int {
get { self.height }
set { self.height = min(newValue, 10) }
}
}
코드를 읽어보면 width
와 heigth
라는 프로퍼티에 같은 로직이 반복해서 들어가게된다.
이 코드는 Property wrapper를 사용하여 반복되는 코드를 줄일 수 있다.
내가 만들 Property wrapper는 LimitedTen
이라는 10보다 작은 Int
값만 들어갈 수 있는 Property wrapper이다.
먼저 LimitedTen
이라는 구조체를 만들고 그 앞에 @propertyWrapper
키워드를 붙여주면 초기 설정은 된 것이다.
Property wrapper를 사용하기 위해서는 wrappedValue
프로퍼티가 정의된 구조체 혹은 클래스를 생성해줘야한다.
설정을 안해주면 위 사진과 같이 컴파일 에러가 뜬다 !
이제 이 구조체를 완성시켜보면 아래 코드와 같다.
@propertyWrapper
struct LimitedTen {
private var value: Int = 0
var wrappedValue: Int {
get { value }
set { self.value = min(newValue, 10) }
}
init(wrappedValue value: Int) {
self.value = value
}
}
여기서 주의해야할 점은 초기화 코드 내부에 파라미터 이름이 wrappedValue
여야한다는 점.ᐟ.ᐟ
그냥 다른 이름으로하면 안된다요..
wrappedValue
프로퍼티가 이 property wrapper를 사용할때 쓰일 값이라고 생각하면 된다.
구조체 내부를 보면 value
라는 프로퍼티를 private
접근 제어자를 설정해주었는데, 이는 LimitedTen
이라는 구조체 내부에서만 실제 값을 저장하고 외부해서 실제 값을 사용하지 못하게 하기 위함이다.
저렇게 해주면 다른 곳에서 작성된 코드는 wrappedValue
를 위한 getter와 setter를 사용하여 값에 접근하고 직접적으로 value
를 사용할 수 없다.
벌써 Property wrapper를 완성했다 !
이제 이 Property wrapper를 직접 적용해보자.
struct Rectangle {
@LimitedTen var width: Int
@LimitedTen var height: Int
}
var rectangle = Rectangle()
print(rectangle.width, rectangle.height) // 0, 0
rectangle.width = 5
print(rectangle.width, rectangle.height) // 5, 0
rectangle.width = 15
print(rectangle.width, rectangle.height) // 10, 0
오옹.ᐟ.ᐟ 겹쳐지는 로직이 싹 정리되어 매우 보기 좋아졌다 .ᐟ.ᐟ ><
위의 LimitedTen 구조체 내부에 초기화 코드를 응용하면 아래와 같은 코드도 만들어줄 수 있다.
@propertyWrapper
struct LimitedNum {
private var value: Int
private var maximum: Int = 10
var wrappedValue: Int {
get { value }
set { self.value = min(newValue, 10) }
}
init(wrappedValue value: Int, maximum: Int = 10) { // 원하는 최대값을 초기화 부분에서 받아 사용할 수 있음
self.maximum = maximum
self.value = min(value, maximum)
}
}
struct Rectangle {
@LimitedNum var width: Int
@LimitedNum(maximum: 20) var height: Int = 15
}
var rectangle = Rectangle(width: 5)
print(rectangle.width, rectangle.height)
// 5, 15
// height의 maximum은 20으로 설정된 상태
rectangle.height = 30
print(rectangle.width, rectangle.height)
// 5, 10
// height를 30으로 설정해주면서 기본 maximum인 10이 들어간 상태로 10이 됨
ProjectedValue
추가로 Property wrapper에는 ProjectedValue라는 재밌는 프로퍼티가 하나 존재한다.
이것은 내가 원하는 값은 wrappedValue와 같이 아무거나 저장할 수 있는 프로퍼티인데, “투영 프로퍼티” 인 만큼 호출할 때, $
키워드를 붙여서 호출할 수 있다.
응용해보면 아래와 같이 사용해볼 수 있다.
@propertyWrapper
struct LimitedNum {
private var value: Int
private var maximum: Int = 10
private(set) var projectedValue: Int
var wrappedValue: Int {
get { value }
set {
self.value = min(newValue, maximum)
self.projectedValue = newValue
}
}
init(wrappedValue value: Int, maximum: Int = 10) {
self.maximum = maximum
self.projectedValue = value
self.value = min(value, maximum)
}
}
struct Rectangle {
@LimitedNum var width: Int
@LimitedNum(maximum: 20) var height: Int = 30
}
var rectangle = Rectangle(width: 5)
print(rectangle.width, rectangle.height) // 5, 20
print(rectangle.$width, rectangle.$height) // 5, 30
위 코드의 경우 바뀐 값은 value를 통해 반환하고 projectedValue를 사용해서 변형되기 이전의 값을 저장해주는 방식으로 사용해봤다 !
정말 잘 활용하면 좋을 것 같지만... Swift의 get
, set
을 잘 활용하지 못하는 나로써는 아직까진 활용을 잘 해보지는 못했던 것 같다 ..! 나중에 프로퍼티 자체에 제약을 줄 일이 생긴다면.. 꼭 사용해봐야겠다 ~