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.