Swift

Swift - 8. 프로퍼티 래퍼

iosos 2023. 12. 29. 18:49

 

 

프로퍼티 래퍼

  • 클래스와 구조체 구현부에 게터(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