ProgrammingiOS Developer

What are Property Wrappers in Swift? How do they work, what are they used for, and what limitations and possibilities exist? Provide an example of creating and using a custom property wrapper.

Pass interviews with Hintsage AI assistant

Answer

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:

  • Property Wrappers cannot be applied to computed properties.
  • Passing arguments during the initialization of the wrapper may be limited.
  • When using multiple wrapped properties within a struct, it works correctly only with value types.

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

Trick Question

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>

Examples of real errors due to misunderstandings of the nuances of the topic


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.