Swift 5.1 introduced property wrappers via SE-0258 to eliminate repetitive accessor boilerplate. The projectedValue requirement was designed to expose secondary API surfaces—such as SwiftUI's Binding or validation states—beyond the wrapped value itself. This feature allows developers to access metadata or projections using the $ prefix syntax.
The problem arises because Swift must transform declarative syntax into valid SIL (Swift Intermediate Language) without introducing name collisions or breaking access control. The compiler needs to synthesize storage that maintains value semantics for the wrapped property while potentially exposing reference semantics through the projection, all while ensuring the $ prefixed identifier does not conflict with user-defined members.
The solution involves source-to-source desugaring. For a property declared as @Wrapper var property: T, the compiler generates three distinct members. First, a private storage variable _property of type Wrapper<T>. Second, a computed property property that forwards get/set operations to _property.wrappedValue. Third, a computed property $property that returns _property.projectedValue. The $ prefixed property inherits the access control of the original declaration, and the compiler enforces that projectedValue exists when the $ syntax is utilized.
@propertyWrapper struct Validating<T> { var wrappedValue: T var projectedValue: ValidationState<T> init(wrappedValue: T) { self.wrappedValue = wrappedValue self.projectedValue = ValidationState(value: wrappedValue) } } // Desugars to: struct Form { private var _username: Validating<String> var username: String { get { _username.wrappedValue } set { _username.wrappedValue = newValue } } var $username: ValidationState<String> { get { _username.projectedValue } } }
We were architecting a medical data entry application where each field needed to track both its current value and a complex validation history including previous errors and correction timestamps. The challenge required exposing two distinct data paths from a single property abstraction: the raw string for the UI text field, and the validation history for analytics and error display.
The first approach considered was maintaining a parallel dictionary mapping property names to ValidationHistory objects. This offered flexibility in storage but introduced stringly-typed APIs that broke during refactoring and required manual synchronization between the dictionary and the actual property values. The risk of desynchronization leading to stale error displays was unacceptably high for medical data.
The second approach involved creating a wrapper struct that contained both the value and history, then using that compound type as the property type. While type-safe, this polluted the domain model with validation concerns and forced every access site to handle the unwrapping, defeating the purpose of clean architecture separation between UI and business logic.
The third approach utilized a custom @Validated property wrapper with a projectedValue returning a ValidationHistory reference type. This encapsulated the synchronization internally while exposing $fieldName for history access. We chose this because it maintained CoW (Copy-on-Write) semantics for the wrapped string value while providing stable reference identity for the validation history, ensuring UI components could observe changes without copying overhead.
The result eliminated an entire class of synchronization bugs and reduced the validation-related codebase by 35%. The $ syntax provided intuitive discoverability for junior developers, and the compile-time enforcement prevented accidental exposure of implementation details across module boundaries.
Why do mutations to a value-type projectedValue not persist when accessed via the dollar-sign prefix?
When the property wrapper is a struct, the projectedValue getter returns a copy of the value. If projectedValue returns a struct (such as an Int or a custom validation state struct), statements like $property.errorCount += 1 mutate a temporary copy that is immediately discarded. To enable persistent mutations, the projectedValue must return a reference type, or the wrapper must implement CoW with a class-based storage backing. Alternatively, return a Binding or mutable pointer that provides indirection. Beginners often assume $property provides mutable access to the wrapper's internal state without considering Swift's value semantics.
How does the access control of the synthesized dollar-sign property interact with the original property's access level?
The compiler synthesizes the $ prefixed property with identical access control to the original property. If you declare public @Wrapper var name: String, both name and $name are public. Conversely, private properties generate private projected values. Candidates frequently attempt to make the wrapped value public while keeping the projected value internal or private, which is impossible in current Swift versions. The workaround requires making the property private and exposing the wrapped value through an explicit computed property, while the projected value remains restricted.
Can a single property wrapper expose multiple distinct projections, and what are the ergonomic implications?
Swift strictly allows only one projectedValue property per wrapper. However, that property can return a tuple, struct, or enum containing multiple values (e.g., projectedValue: (Binding<T>, ValidationError?, Bool)). The ergonomic trade-off is that $property then requires dot-syntax to access components ($property.0, $property.isValid), reducing readability. Some candidates attempt to declare multiple projectedValue properties or apply multiple property wrappers to the same property (chaining). While chaining is supported, it creates complex initialization semantics and opaque type inference issues. The recommended approach for multiple projections is returning a dedicated projection struct with named properties, preserving type safety while accepting the syntax overhead.