프로퍼티 래퍼는 스위프트 5.1부터 나온 개념으로, 클래스와 구조체 구현부에 게터(getter), 세터(setter), 연산 프로퍼티(computed property) 코드의 중복을 줄이는 방법을 제공한다.
프로퍼티 래퍼란?
클래스나 구조체 인스턴스에 값을 할당하거나 접근할 때 값을 저장하거나 읽어내기 전에 변환 작업이나 유효성 검사를 해야 할 경우가 존재한다. 물론 연산 프로퍼티로도 위와 같은 작업은 할 수 있다. 그러나 여러 클래스나 구조체에 생성한 연산 프로퍼티들이 유사한 패턴을 갖는 경우가 빈번하게 발생한다.
클래스나 구조체의 구현부마다 비슷한 역할을 하는 연산 프로퍼티를 복사 붙여넣기 할 수도 있다. 이것은 생산성이 매우 떨어질 뿐만 아니라, 계산 방법이 수정되는 일이 생기면 각각의 클래스나 구조체에 복사해둔 연산 프로퍼티를 일일이 찾아 직접 수정해야 했다.
아래 예제를 보자
struct User {
private var _name : String = ""
var name : String {
get {
_name
}
set {
_name = newValue.uppercased()
}
}
}
위 User 구조체는 유저 이름 프로퍼티를 가진다. 그러나 이름을 어떻게 입력하든 항상 대문자로 저장하고 싶은 경우가 있다. 이 때 연산 프로퍼티를 사용해서, 새로운 값을 set 하기 전에 모두 대문자로 변환하는 작업을 거칠 수 있다.
struct User {
private var _name : String = ""
var name : String {
get {
_name
}
set {
_name = newValue.uppercased()
}
}
}
var user = User()
user.name = "Park Jong Ho"
print(user.name)
위의 코드를 실행하면, "PARK JONG HO" 가 출력되는 것을 알 수 있다.
프로퍼티 래퍼 구현하기
연산 프로퍼티로도 충분히 동일한 기능을 구현할 수 있지만, 코드의 재사용성을 고려해볼 때 그리 현명한 방법은 아니다. 프로퍼티 래퍼를 사용하면, 재사용이 가능한 코드를 만들 수 있다.
동일한 로직을 하는 프로퍼티 래퍼를 구현해보자
@propertyWrapper
struct FixCase{
private(set) var value : String = ""
var wrappedValue : String {
get {
value
}
set {
value = newValue.uppercased()
}
}
init(wrappedValue initialValue: String ){
self.wrappedValue = initialValue
}
}
- 프로퍼티 래퍼는 @propertyWrapper 지시자를 사용해 선언된다.
- 프로퍼티 래퍼는 클래스나 구조체 안에 구현된다.
- 모든 프로퍼티 래퍼는 값을 변경하거나 유효성을 검사하는 게터와 세터 코드가 포함된 wrappedValue 프로퍼티를 가져야 한다.
- 초기화 메소드는 선택사항으로 포함될 수 있다.
이제 프로퍼티 래퍼를 선언했으니 이 프로퍼티 래퍼를 사용해보자
struct User {
@FixCase var name : String = ""
}
var user = User()
user.name = "Park Jong Ho"
print(user.name)
//출력결과
PARK JONG HO
위와 같이 @{프로퍼티 래퍼 이름} 지시자를 사용한 프로퍼티를 선언함으로써, 해당 프로퍼티 래퍼를 사용할 수 있다.
여러 변수와 타입 지원하기
어떤 작업을 수행할 때 사용될 여러 값을 받도록 더 복잡한 프로퍼티 래퍼를 구현할 수도 있다. 추가되는 값들은 프로퍼티 래퍼 이름 다음의 괄호 안에 둔다.
예를 들어 출생연도를 나타내는 프로퍼티를 생각해보자. 이 글을 쓰는 시점이 2022년이므로, 2022년 보다 큰 값은 출생연도로 들어올 수 없다.
따라서 출생연도를 1900 ~ 2022 에 해당하는 값만 받도록 프로퍼티 래퍼를 구현해보자
@propertyWrapper
class MinMaxValue {
let min : Int
let max : Int
private(set) var value : Int
var wrappedValue : Int {
get {
value
}
set {
if newValue >= min && newValue <= max {
value = newValue
} else if newValue > max {
value = max
} else {
value = min
}
}
}
init(wrappedValue initialValue : Int,min : Int,max : Int){
value = initialValue
self.min = min
self.max = max
}
}
위 프로퍼티 래퍼는 wrappedValue 말고도 추가적으로 min 과 max 라는 프로퍼티를 가지고 있다. 그리고 이 프로퍼티 범위 안에 있는 값만 프로퍼티에 할당한다.
struct User {
@MinMaxValue(min: 1900, max: 2022) var birthYear : Int = 1006
}
이제 1900 ~ 2022 까지의 값만 받도록 프로퍼티 래퍼로 감싸 프로퍼티를 선언할 수 있다.
var user = User()
user.birthYear = 3000
print(user.birthYear)
//출력결과
2022
birthYear 프로퍼티에 3000이란 값을 할당해도 setter 에 의해 2022 라는 max 값으로 할당되는 것을 확인할 수 있다.
제네릭을 사용한 프로퍼티 래퍼 구현
위의 프로퍼티 래퍼는 오로지 Int 타입만을 지원한다. 하지만 코드의 재사용성을 극대화하기 위해서 제네릭 타입을 사용해서 구현할 수 있다. Comparable 프로토콜을 따르는 모든 데이터 타입은 비교 연산이 가능하므로, Comparable 프로토콜을 구현하는 타입을 제네릭으로 받도록 프로퍼티 래퍼를 수정할 수 있다.
@propertyWrapper
class MinMaxValue<V:Comparable> {
let min : V
let max : V
private(set) var value : V
var wrappedValue : V {
get {
value
}
set {
if newValue >= min && newValue <= max {
value = newValue
} else if newValue > max {
value = max
} else {
value = min
}
}
}
init(wrappedValue initialValue : V,min : V,max : V){
value = initialValue
self.min = min
self.max = max
}
}
위 프로퍼티 래퍼는 Comparable 프로토콜을 구현하는 모든 타입을 지원하므로, Date 나 Char 도 사용할 수 있다.
'Swift' 카테고리의 다른 글
Swift 에러 핸들링 (0) | 2022.02.11 |
---|---|
Swift 딕셔너리 (0) | 2022.02.10 |
구조체와 클래스 (0) | 2022.02.10 |
Swift 함수 - 2 (0) | 2022.02.09 |
Swift 5 - 함수 1 (0) | 2022.02.09 |
Comment