Property Wrappers and Result Builders (with version cues)#
Practical usage of property wrappers and result builders, with version highlights.
Version Quick Reference#
- Swift 5.1: property wrappers introduced.
- Swift 5.4: result builders stabilized (formerly function builders).
- Swift 5.9+: macros arrive (not covered here); builders/wrappers remain common for DSLs.
- Swift 6+: stricter concurrency checking; wrappers interacting with concurrency may need
Sendable/@MainActor.
Property Wrappers Basics#
- Wrap storage/behavior behind a projected value. Use
@propertyWrapper on a struct/class/enum.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| @propertyWrapper
struct Clamped<Value: Comparable> {
private var value: Value
let range: ClosedRange<Value>
init(wrappedValue: Value, _ range: ClosedRange<Value>) {
self.range = range
self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
}
var wrappedValue: Value {
get { value }
set { value = min(max(newValue, range.lowerBound), range.upperBound) }
}
}
struct Volume {
@Clamped(0...100) var level = 50
}
|
- Access with
_level to reach the storage, $level for the projected value (customizable via projectedValue).
Common SwiftUI Wrappers (recap)#
@State, @Binding, @ObservedObject, @StateObject, @EnvironmentObject, @AppStorage, @SceneStorage.- Remember: choose based on ownership/lifetime (see
swiftui-state.md).
Result Builders#
- Builders collect expressions into a composed result (e.g., SwiftUI
ViewBuilder).
1
2
3
4
5
6
7
8
9
10
11
| @resultBuilder
struct StringListBuilder {
static func buildBlock(_ components: String...) -> [String] { components }
}
@StringListBuilder
func makeStrings() -> [String] {
"one"
if Bool.random() { "two" }
for i in 0..<2 { "loop \(i)" }
}
|
- Builders support
buildBlock, buildOptional, buildEither, buildArray, and optionally buildFinalResult. - Use
@ViewBuilder/@SceneBuilder (iOS 13+) and @ToolbarContentBuilder (iOS 14+) in SwiftUI.
Concurrency Considerations#
- If wrapper storage crosses actors/threads, consider
Sendable and isolation. For UI wrappers, mark with @MainActor when needed. - Avoid heavy work in getters/setters of wrappers; keep them lightweight to avoid surprising costs.
Testing#
- For wrappers, test boundary cases on the wrapped/projection values.
- For builders, add small functions that must type-check to catch regressions when changing builder signatures.