Introduction
Composing property wrappers lets you combine multiple behaviors on one property easily. It helps keep code clean and reusable.
Composing property wrappers lets you combine multiple behaviors on one property easily. It helps keep code clean and reusable.
@Wrapper2 @Wrapper1 var propertyName: Type@Logged @Clamped(min: 0, max: 10) var score: Int = 5
@UserDefault("username") @Trimmed var name: String = ""
This program shows two property wrappers combined. The health property is clamped between 0 and 100, and changes are logged.
import Foundation @propertyWrapper struct Clamped { private var value: Int let min: Int let max: Int var wrappedValue: Int { get { value } set { value = Swift.min(Swift.max(newValue, min), max) } } init(wrappedValue: Int, min: Int, max: Int) { self.min = min self.max = max self.value = Swift.min(Swift.max(wrappedValue, min), max) } } @propertyWrapper struct Logged { private var storage: Clamped var wrappedValue: Int { get { storage.wrappedValue } set { let oldValue = storage.wrappedValue storage.wrappedValue = newValue print("Changing value from \(oldValue) to \(storage.wrappedValue)") } } init(wrappedValue: Int) { self.storage = Clamped(wrappedValue: wrappedValue, min: 0, max: 100) } init(wrappedValue: Clamped) { self.storage = wrappedValue } } struct Player { @Logged @Clamped(min: 0, max: 100) var health: Int = 50 } var player = Player() player.health = 120 player.health = -10 player.health = 80
Order matters: the wrapper closest to the property applies first.
Each wrapper can add its own logic like validation, logging, or storage.
Composing wrappers keeps code modular and easier to maintain.
Composing property wrappers means stacking multiple wrappers on one property.
They apply behaviors in order, from right to left.
This technique helps add multiple features cleanly and reuse code.
@WrapperA and @WrapperB on a property var value: Int such that @WrapperA is applied first (innermost)?@WrapperB @WrapperA var value: Int applies @WrapperA first, then @WrapperB. @propertyWrapper
struct WrapperA {
var wrappedValue: Int
init(wrappedValue: Int) { self.wrappedValue = wrappedValue + 1 }
}
@propertyWrapper
struct WrapperB {
var wrappedValue: Int
init(wrappedValue: Int) { self.wrappedValue = wrappedValue * 2 }
}
struct Example {
@WrapperA @WrapperB var value: Int = 3
}Example().value?@WrapperB applies: 3 * 2 = 6.@WrapperA next@WrapperA adds 1: 6 + 1 = 7. @propertyWrapper
struct WrapperX {
var wrappedValue: Int
init(wrappedValue: Int) { self.wrappedValue = wrappedValue + 5 }
}
struct Test {
@WrapperX @WrapperX var number: Int = 10
} @propertyWrapper
struct Clamped {
private var value: Int
let range: ClosedRange<Int>
var wrappedValue: Int {
get { value }
set { value = min(max(newValue, range.lowerBound), range.upperBound) }
}
init(wrappedValue: Int, _ range: ClosedRange<Int>) {
self.range = range
self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
}
}
@propertyWrapper
struct Logged {
private var value: Int
var wrappedValue: Int {
get { value }
set {
print("Value changed to \(newValue)")
value = newValue
}
}
init(wrappedValue: Int) {
self.value = wrappedValue
}
}
struct Settings {
@Logged @Clamped(0...100) var score: Int = 150
}Settings().score?@Clamped(0...100) clamps 150 to 100 in init.@Logged receives 100@Logged init sets value=100 directly (no setter called, no print).Pass the test and this pattern moves unknown