프로퍼티 래퍼
- 클래스와 구조체 구현부에 게터(getter), 세터(setter), 연산 프로퍼티(computed property) 코드의 중복을 줄여줌
- 연산 프로퍼티의 기능을 개별 클래스와 구조체와 분리할 수 있게 함 → 앱 코드에서 재사용
- 도시 이름을 저장하는 String 프로퍼티를 가지는 구조체
struct Address {
var city : String
}
- 사용자 입력에 상관없이 도시 이름이 대문자로 저장되어야 한다면 다음과 같이 연산 프로퍼티 추가
struct Address {
private cityname : String = ""
var city : String {
get {cityname}
set {cityname = newValue.uppercased()}
}
}
var address = Address()
address.city = "London"
print(address.city) // LONDON
- 도시 이름이 프로퍼티에 할당되면 연산 프로퍼티의 세터(setter)가 private cityname 변수에 값을 저장하기 전에 대문자로 변환
→ 이와 동일한 작업이 다른 구조체나 클래스에 필요하다면 코드를 복사 붙여넣기 하는 방법이 있음
그러나 적절하지 않음
- 연산 프로퍼티 대신 위 로직을 프로퍼티 래퍼로 구현해야 함
문자열을 대문자로 변환하도록 설계된 FixCase라는 프로퍼티 래퍼 구현
// 프로퍼티 래퍼 정의
@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 프로퍼티를 가져야 함
- 초깃값이 전달되는 초기화 메서드는 선택사항으로 포함될 수 있음
- 초깃값 문자열을 대문자로 변환하고 private 변수에 저장하는 wrappedValue 프로퍼티에 할당
프로퍼티 래퍼 사용
struct Contact {
@FixCase var name : String
@FixCase var city : String
@FixCase var country : String
}
var contact = Contact(name: "John Smith", city: "London", country: "United Kingdom")
print("\(contact.name), \(contact.city), \(contact.country)") // JOHN SMITH, LONDON, UNITED KINGDOM
- 이 동작이 필요한 클래스나 구조체의 선언부에 있는 프로퍼티 선언 앞에 @FixCase 지시자를 붙임
여러 변수와 타입 지원하기
- 복잡한 프로퍼티 래퍼 구현
- 추가되는 값들은 프로퍼티 래퍼 이름 다음의 괄호 안에 둠
- 지정된 값으로 사용하도록 설계된 프로퍼티 래퍼는 다음의 형태와 같음
@propertyWrapper
struct MinMaxVal{
var value : Int
let max : Int
let min : Int
init(wrappedValue : Int, min : Int, max : Int){
value = wrappedValue
self.min = min
self.max = max
}
var wrappedValue : Int {
get { return value }
set {
if newValue > max {
value = max
} else if newValue < min {
value = min
} else {
value = newValue
}
}
}
}
struct Demo {
@MinMaxVal(min : 10, max : 150) var value : Int = 100
}
var demo = Demo()
demo.value = 140
print(demo.value) // 140
demo.value = 250
print(demo.value) // 150
- init () 메서드는 래퍼 값에 추가된 min, max 값을 받아서 구현
- wrappedValue 세터는 값이 특정 범위 안에 있는지 검사하여 그 값을 min 또는 max에 할당
다양한 변수 타입 사용
- 프로퍼티 래퍼는 특정 프로토콜을 따르는 모든 타입과 작업하도록 구현할 수 있음
- 프로퍼티 래퍼의 목적 : 비교 작업
- Foundation 프레임워크에 포함된 Comparable 프로토콜을 따르는 모든 데이터 타입을 지원하도록 수정해야 함
- Comparable 프로토콜을 따르는 타입은 값을 비교하는 데 사용할 수 있음
- String, Int, Date, DateInterval, Character 등 다양한 타입이 이 프로토콜을 따름
- Comparable 프로토콜을 따르는 모든 타입에 사용될 수 있도록 프로퍼티 래퍼를 구현하기 위해서는 선언부를 수정해야 함
import Foundation
@propertyWrapper
struct MinMaxVal<V : Comparable>{
var value : V
let max : V
let min : V
init(wrappedValue : V, min : V, max : V){
value = wrappedValue
self.min = min
self.max = max
}
var wrappedValue : V {
get { return value }
set {
if newValue > max {
value = max
} else if newValue < min {
value = min
} else {
value = newValue
}
}
}
}
- 문자열 값이 알파벳 관점에서 쵯소값과 최댓값 범위 내에 있는지 판단
struct Demo {
@MinMaxVal(min : "Apple", max : "Orange") var value : String = ""
}
var demo = Demo()
demo.value = "Banana"
print(demo.value) // Banana - 주어진 알파벳 범위 내에 있어서 저장됨
demo.value = "Pear"
print(demo.value) // Orange - 주어진 알파벳 범위 밖이므로 지정된 최댓값으로 대체
- 현재 날짜와 한 달 후 날짜 사이의 데이터 제한
struct Demo {
@MinMaxVal(min : Date(), max : Calendar.current.date(byAdding: .month, value : 1, to: Date())!) var value : Date = Date()
}
var demo = Demo()
print(demo.value) // 2023-12-14 12:24:18 +0000 -> 디폴트로 현재 날짜가 프로퍼티에 설정
// 프로퍼티에 10일 후의 날짜를 설정
demo.value = Calendar.current.date(byAdding: .day, value : 10, to: Date())!
print(demo.value) // 2023-12-24 12:24:18 +0000 -> 유효 범위 내이므로 프로퍼티에 저장
// 프로퍼티에 2달 후의 날짜를 설정
demo.value = Calendar.current.date(byAdding: .month, value : 2, to: Date())!
print(demo.value) // 2024-01-14 12:24:18 +0000 -> 유효 범위 밖이므로 최댓값이 저장
'Swift' 카테고리의 다른 글
Swift - 10. 에러 핸들링 (0) | 2024.01.06 |
---|---|
Swift - 9. 배열과 딕셔너리 (1) | 2024.01.06 |
Swift - 7. 구조체 (0) | 2023.12.28 |
Swift - 6. 서브 클래싱과 익스텐션 개요 (0) | 2023.12.28 |
Swift - 5. 객체 지향 프로그래밍 기초 (0) | 2023.12.27 |