Property Wrappers and Result Builders
Prerequisites: Generics, Closures
Two Swift features let library authors create tidy, declarative APIs.
You use them constantly in SwiftUI (@State, VStack { ... }), so
it's worth knowing how they work.
Property wrappers
A property wrapper packages the logic for how a property is stored and accessed, so it can be reused with a single annotation:
@propertyWrapper
struct Clamped {
private var value: Int
let range: ClosedRange<Int>
init(wrappedValue: Int, _ range: ClosedRange<Int>) {
self.range = range
self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
}
var wrappedValue: Int {
get { value }
set { value = min(max(newValue, range.lowerBound), range.upperBound) }
}
}
struct Speaker {
@Clamped(0...10) var volume = 5
}
var speaker = Speaker()
speaker.volume = 15
print(speaker.volume)
speaker.volume = -3
print(speaker.volume)
10
0
@propertyWrappermarks the type;wrappedValueis what the property reads and writes.@Clamped(0...10) var volume = 5callsClamped(wrappedValue: 5, 0...10).- A wrapper can also expose a projected value, accessed with
$— that's how SwiftUI's$nameproduces a binding from@State var name.
Result builders
A result builder turns a closure containing a list of expressions into a single combined value:
@resultBuilder
struct ListBuilder {
static func buildBlock(_ items: String...) -> [String] {
items
}
}
func shoppingList(@ListBuilder _ content: () -> [String]) -> [String] {
content()
}
let list = shoppingList {
"Milk"
"Eggs"
"Bread"
}
print(list)
["Milk", "Eggs", "Bread"]
Each line in the closure becomes an argument to buildBlock. Builders
can also support if, switch, and for by adding methods such as
buildOptional, buildEither, and buildArray.
SwiftUI's @ViewBuilder is a result builder — which is why you can list
views one after another inside a VStack without commas or arrays.
When to write your own
You'll use both features constantly, but write your own rarely. A property wrapper earns its keep when the same property logic repeats across many types; a result builder when you're designing a DSL for others to use.