SwiftUI

[Swift] 스위프트 프로퍼티 래퍼 @propertyWrapper 지시자 사용방법 및 예제 코드


프로퍼티 래퍼는 기본적으로 연산 프로퍼티의 기능을 개별 클래스와 구조체와 분리할 수 있게 하며, 앱 코드에서 재사용할 수 있게 한다.

다음과 같이 도시 이름을 저장하는 String 프로퍼티를 가진 구조체가 있다고 하자.

struct Address {    
   var city: String
}

사용자가 도시 이름을 어떻게 입력했지와는 상관없이 대문자로 저장되어야 한다면 다음과 같이 연산 프로퍼티를 구조체에 추가할 수 있다.

struct Address {   
   private var cityname: String = ""   
   var city: String {       
       get { cityname }        
       set { 
           cityname = newValue.uppercased()
       }   
   }
}

// 코드 실행 
var address = Address()
address.city = "London"
print(address.city)

연산 프로퍼티를 사용하는 대신에 이 로직을 프로퍼티 래퍼로 구현할 수 있다. 예를 들어, 다음의 선언부는 문자열을 대문자로 변환하도록 설계된 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 프로퍼티를 가져야 한다. 초깃값이 전달되는 초기화 메서드는 선택 사항으로 포함될 수도 있다.

이제 프로퍼티 래퍼에 대한 정의가 끝났으니 이와 동일한 동작이 필요한 다른 프로퍼티 변수에 적용하여 재사용할 수 있다. 프로퍼티 래퍼를 사용하기 위해서는 이 동작이 필요한 클래스나 구조체의 선언부에 있는 프로퍼티 선언 앞에 @FixCase 지시자를 붙이면 된다.

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)")

여러 변수와 타입 지원하기

프로퍼티 래퍼의 목적은 비교 작업을 하는 것이므로, Foundation 프레임워크에 포함된 Comparable 프로토콜을 따르는 모든 데이터 타입을 지원하도록 수정해야 한다. Comparable 프로토콜을 따르는 타입은 값이 같은지, 더 큰지, 더 작은지를 비교하는 데 사용될 수 있다. String, Int, Date, DateInterval, 그리고 Character 같은 다양한 타입이 이 프로토콜을 따른다.

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

이렇게 수정된 래퍼는 앞에서 했던 것처럼 Int 값으로도 동작하며, Comparable 프로토콜을 따르는 다른 모든 타입에도 사용할 수가 있다. 다음의 예제는 문자열 값이 알파벳 관점에서 최솟값과 최댓값 범위 안에 들어오는지를 판단하는 것이다.

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 <--- 이 값은 주어진 알파벳 범위 밖이므로 지정한 최댓값으로 대체된다. 

마찬가지로, 이 래퍼는 Date 객체로도 동작을 한다. 다음의 예제는 현재 날짜와 한 달 후 날짜 사이의 데이터로 제한하고 있다.

struct DateDemo {
   @MinMaxVal(min: Date(), max: Calendar.current.date(byAdding: .month, value: 1, to:Date())! ) var value: Date = Date()
}

다음의 코드와 결과는 우리가 만든 래퍼가 Date 값을 가지고도 동작하는 것을 보여준다.

var dateDemo = DateDemo()
print(dateDemo.value)
// 2019-08-23 20:05:13 +0000. <--- 디폴트로 현재 날짜가 프로퍼티에 설정되었다

dateDemo.value = Calendar.current.date(byAdding: .day, value: 10, to: Date())! 
// <--- 프로퍼티에 10일 후의 날짜를 설정한다.
print(dateDemo.value)
// 2019-09-02 20:05:13 +0000 <--- 유효 범위 내에 있으므로 프로퍼티에 저장된다
 

dateDemo.value = Calendar.current.date(byAdding: .month, value: 2, to: Date())!
// <---프로퍼티에 2달 후의 날짜를 설정한다

print(dateDemo.value)
// 2019-09-23 20:08:54 +0000 <--- 유효 범위 밖이므로 프로퍼티에는 최댓값(1달)이 저장된다
 

-알라딘 eBook <핵심만 골라 배우는 SwiftUI 기반의 iOS 프로그래밍> (닐 스미스 지음, 황반석 옮김) 중에서

-알라딘 eBook <핵심만 골라 배우는 SwiftUI 기반의 iOS 프로그래밍> (닐 스미스 지음, 황반석 옮김) 중에서

Leave a Reply

error: Content is protected !!