Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

A property wrapper is a compiler-synthesized mechanism that introduces a layer of separation between the code that manages how a property is stored and the code that defines the property. By applying the @propertyWrapper attribute to a custom type, you define a reusable encapsulation strategy for property access and mutation. To create a property wrapper, a struct, class, or enum must be annotated with the @propertyWrapper attribute and must implement a non-static property named wrappedValue.
@propertyWrapper
struct CustomWrapper<Value> {
    private var storage: Value
    
    init(wrappedValue: Value) {
        self.storage = wrappedValue
    }
    
    var wrappedValue: Value {
        get { return storage }
        set { storage = newValue }
    }
}

Compiler Desugaring

When you apply a property wrapper to a declaration, the Swift compiler automatically generates a backing storage variable and routes all access to the original property through the wrapper’s wrappedValue.
struct Model {
    @CustomWrapper var data: Int = 0
}
Under the hood, when applied to a property on a type, the compiler desugars the declaration into the following equivalent code:
struct Model {
    // 1. Private backing storage initialized with the wrapper type
    private var _data: CustomWrapper<Int> = CustomWrapper<Int>(wrappedValue: 0)

    // 2. Computed property exposing the wrappedValue
    var data: Int {
        get { return _data.wrappedValue }
        set { _data.wrappedValue = newValue }
    }
}
When a property wrapper is applied to a local variable within a function, the compiler synthesizes the same backing storage and computed access, but access control modifiers (like private) are omitted since they do not apply to local scopes.

Initialization Semantics

Property wrappers support implicit initialization at the call site if the wrapper type defines an init(wrappedValue:) initializer. When a default value is assigned to the wrapped property, the compiler translates this assignment into a call to this specific initializer.
@propertyWrapper
struct InitializableWrapper<Value> {
    var wrappedValue: Value
    
    init(wrappedValue: Value) {
        self.wrappedValue = wrappedValue
    }
}

struct InitializationModel {
    // The compiler translates this to: 
    // private var _count = InitializableWrapper(wrappedValue: 10)
    @InitializableWrapper var count: Int = 10
}
Wrappers can also define custom initializers to accept additional arguments. These are passed in the attribute declaration syntax.
@propertyWrapper
struct ConfiguredWrapper<Value> {
    var wrappedValue: Value
    
    init(wrappedValue: Value, configuration: String) {
        self.wrappedValue = wrappedValue
    }
}

struct ConfiguredModel {
    @ConfiguredWrapper(configuration: "strict") var count: Int = 10
}

Projected Values

A property wrapper can optionally expose a secondary API by defining a projectedValue property. The compiler synthesizes access to this projected value by prefixing the original property name with a dollar sign ($). The projectedValue can return any type, including self (the wrapper instance itself), allowing external code to interact with the wrapper’s internal state or auxiliary methods.
@propertyWrapper
struct ProjectedWrapper<Value> {
    var wrappedValue: Value
    
    var projectedValue: ProjectedWrapper<Value> {
        return self
    }
    
    func auxiliaryMethod() { }
}

struct ProjectedModel {
    @ProjectedWrapper var data: Int = 0
    
    func accessProjection() {
        let value: Int = data                           // Accesses wrappedValue
        let wrapper: ProjectedWrapper<Int> = $data      // Accesses projectedValue
        
        $data.auxiliaryMethod()
    }
}

Architectural Constraints

  • Protocols: Property wrappers cannot be applied to property requirements inside a protocol declaration (e.g., protocol P { @Wrapper var x: Int { get } } is invalid).
  • Property Modifiers: A property wrapper cannot be applied to lazy, weak, unowned, or computed properties.
  • Access Control:
    • Backing Storage: When applied to a type’s property, the synthesized backing storage (_propertyName) is always private. When applied to a local variable, access control modifiers do not apply.
    • Wrapped and Projected Values: The synthesized properties (propertyName and $propertyName) share the access level of the original property declaration. Consequently, the wrapper’s underlying wrappedValue and projectedValue implementations must be at least as accessible as the wrapped property. If a property is declared public but the wrapper’s projectedValue is internal, the compiler does not silently downgrade the projection’s access level; instead, it emits a compile-time error.
Master Swift with Deep Grasping Methodology!Learn More