Swift의 Property Wrapper(프로퍼티 래퍼) 정의와 사용법

Swift의 Property Wrapper(프로퍼티 래퍼)로 프로퍼티를 정의하고 사용하는 방법을 배워보세요.


Swift의 Property Wrapper(프로퍼티 래퍼) 정의와 사용법

Swift에서 Property Wrapper 이란?

Property Wrapper는 Class 또는 Struct 인스턴스의 프로퍼티에 값을 할당할 때 유효성 검사를 하기 위해 게터(getter)와 세터(setter), 연산 프로퍼티(computed property)를 추가하는 것을 캡슐화하여 코드를 간결하고 재사용 가능하게 합니다.

Property Wrapper의 기본구조

Property Wrapper는 @propertyWrapper 키워드를 사용해 정의하며, wrappedValue라는 필수 속성을 포함합니다.

Property Wrapper를 정의하는 방법은 다음과 같습니다.

@propertyWrapper
struct Capitalized {
    private var value: String = ""
 
    var wrappedValue: String {
        get { value }
        set { value = newValue.capitalized }
    }
 
    init(wrappedValue: String) {
        self.wrappedValue = wrappedValue
    }
}

위에서 사용한 @Capitalized을 사용하면 속성 값을 자동으로 대문자로 변환합니다.

struct Person {
    @Capitalized var name: String
}
 
let person = Person(name: "john")
print(person.name) // John

Property Wrapper의 활용

1. 데이터 검증

사용자 입력값 검증에 활용할 수 있습니다.

@propertyWrapper
struct Clamped<Value: Comparable> {
    private var value: Value
    private let range: ClosedRange<Value>
 
    var wrappedValue: Value {
        get { value }
        set { value = min(max(newValue, range.lowerBound), range.upperBound) }
    }
 
    init(wrappedValue: Value, range: ClosedRange<Value>) {
        self.range = range
        self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
    }
}
 
struct Settings {
    @Clamped(range: 0...100) var brightness: Int = 50
}
 
var settings = Settings()
settings.brightness = 120
print(settings.brightness) // 100

2. 로깅

값이 변경될 때마다 기록을 남길 수 있다.

@propertyWrapper
struct Logged<Value> {
    private var value: Value
 
    var wrappedValue: Value {
        get { value }
        set {
            print("Value changed from \(value) to \(newValue)")
            value = newValue
        }
    }
 
    init(wrappedValue: Value) {
        self.value = wrappedValue
    }
}
 
struct Logger {
    @Logged var action: String
}
 
var logger = Logger(action: "Start")
logger.action = "Pause"
// Value changed from Start to Pause
logger.action = "Stop"
// Value changed from Pause to Stop

3. 캐싱

값을 캐싱하여 계산 비용을 줄일 수 있습니다.

@propertyWrapper
class Cached<Value> {
    private var value: Value?
    private let computation: () -> Value
 
    var wrappedValue: Value {
        if let value = value {
            return value
        } else {
            let computed = computation()
            value = computed
            return computed
        }
    }
 
    init(wrappedValue: @escaping () -> Value) {
        self.computation = wrappedValue
    }
}
 
struct DataManager {
    @Cached { expensiveCalculation() } var cachedValue: Int
}
 
func expensiveCalculation() -> Int {
    print("Expensive calculation performed")
    return 42
}
 
var manager = DataManager()
print(manager.cachedValue) // Expensive calculation performed \n 42
print(manager.cachedValue) // 42