Property Wrappers are a mechanism that allows encapsulating the logic of working with properties (for example, validation, changes, or storage in a specific way) and reusing it for various properties through annotations in the code. They help eliminate repetitive code and enhance readability.
A Property Wrapper is a struct, class, or enum that implements the property wrapper protocol via the @propertyWrapper annotation and the mandatory wrappedValue property.
Limitations and nuances:
Example: Let's write a property wrapper for automatic clamping of a value range (Clamped).
@propertyWrapper struct Clamped<Value: Comparable> { var value: Value let range: ClosedRange<Value> var wrappedValue: Value { get { value } set { value = min(max(newValue, range.lowerBound), range.upperBound) } } init(wrappedValue initialValue: Value, _ range: ClosedRange<Value>) { self.range = range self.value = min(max(initialValue, range.lowerBound), range.upperBound) } } struct Person { @Clamped(0...120) var age: Int = 25 } var p = Person() p.age = 200 // Now p.age = 120 p.age = -10 // Now p.age = 0
What access method to the original property wrapper object is available outside the struct/class where it is applied?
Answer: Through the property name prefixed with an underscore (_). For example, if the property is named age, the property wrapper object can be accessed as _age:
var p = Person() let wrapper = p._age // this is of type Clamped<Int>
Story
In a project for storing UserDefaults, a custom property wrapper was implemented that worked with primitive types. When using it for reference types (classes), an unexpected memory leak occurred — the property wrapper held a strong reference to the object, resulting in a strong cycle and data leak. The error was fixed by moving to a weak/unowned reference within the wrapper.
Story
In the project, they attempted to apply a property wrapper to computed properties, but the compiler threw an error: the property wrapper can only be used with stored properties. This fact was overlooked, which delayed the development of the module by 2 days.
Story
When creating the wrapper struct, they forgot to implement the correct initialization syntax via init(wrappedValue:...). As a result, it was not possible to set default values through the property wrapper, which became apparent only after integrating the wrapper into a large number of models. The architecture had to be reconsidered.