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