Reading — step 1 of 6
Learn
~2 min readProtocol-Oriented Programming
Property wrappers (Swift 5.1+) let you separate the storage of a property from its access pattern. They're how SwiftUI's @State, @Binding, @Published work — and a powerful tool for any cross-cutting property logic.
The basic shape
@propertyWrapper
struct Trimmed {
private var value: String = ""
var wrappedValue: String {
get { value }
set { value = newValue.trimmingCharacters(in: .whitespaces) }
}
init(wrappedValue: String) {
self.value = wrappedValue.trimmingCharacters(in: .whitespaces)
}
}
struct User {
@Trimmed var name: String
}
var u = User(name: " Alice ")
print(u.name) // "Alice"
u.name = " Bob "
print(u.name) // "Bob"
The wrapper intercepts get/set. Anyone using name writes plain assignments; the wrapper applies the trimming silently.
Validating values
@propertyWrapper
struct Clamped<T: Comparable> {
private var value: T
private let range: ClosedRange<T>
var wrappedValue: T {
get { value }
set { value = min(max(newValue, range.lowerBound), range.upperBound) }
}
init(wrappedValue: T, _ range: ClosedRange<T>) {
self.range = range
self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
}
}
struct Player {
@Clamped(0...100) var health: Int = 100
}
var p = Player()
p.health = 150 // clamped to 100
p.health = -50 // clamped to 0
print(p.health) // 0
Projected values with $
Wrappers can expose a SECONDARY value via projectedValue:
@propertyWrapper
struct Logged<T> {
private var value: T
private(set) var history: [T] = [] // projected value
var wrappedValue: T {
get { value }
set { value = newValue; history.append(newValue) }
}
var projectedValue: [T] { history }
init(wrappedValue: T) { self.value = wrappedValue }
}
struct Counter {
@Logged var count: Int = 0
}
var c = Counter()
c.count = 5
c.count = 10
c.count = 7
print(c.count) // 7
print(c.$count) // [0, 5, 10, 7] — accessed via $
SwiftUI's wrappers
SwiftUI uses property wrappers extensively:
@State— UI-local mutable state in views@Binding— two-way connection to parent state@Published— forObservableObjectproperties@Environment— pull values from the environment@FetchRequest— Core Data queries
Understanding wrappers makes SwiftUI feel less magical.
Common mistakes
- Confusing
wrappedValuewith the projected$value —obj.xis wrappedValue;obj.$xis projectedValue. - Using property wrappers on lazy/computed properties — they don't work; wrappers ARE storage.
- Wrappers in protocol requirements — you can't directly require a property be wrapped. Use a regular property requirement and adopt wrappers in conforming types.
- Excessive wrapping — every @ adds indirection. Use wrappers for genuinely cross-cutting concerns, not minor boilerplate.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…