Property Wrappers in Swift
Property wrappers were introduced in Swift 5.1 to eliminate boilerplate code, facilitate code reuse, and enable more expressive APIs.
Why do we need Property Wrappers?
A property wrapper adds a layer of separation between the code that manages how a property is stored and the code that defines a property.
For example, if you have properties that provide thread-safety checks or store their underlying data in a database, you have to write that code on every property.
When you use a property wrapper, you write the management code once when you define the wrapper, and then reuse that management code by applying it to multiple properties.
To use property wrappers is a choice. But once you are aware of the property wrappers & their benefits, then you will understand its addition to the Swift language.
It’s a neat addition to the Swift library that allows removing much boilerplate code, which we probably all have written in our projects.
What is property wrapper?
To define a property wrapper, you make a structure, enumeration, or class that defines a wrappedValue property.
Property Wrappers in Swift allow you to extract common logic in a distinct wrapper object.
You can see a property wrapper as an extra layer that defines how a property is stored or computed on reading.
Property wrappers were originally named property delegates, inspired by Kotlin’s delegated properties.
Anatomy of Property Wrapper
To define a property wrapper, you can use the annotation @propertyWrapper
Property wrapper is just like any other struct in Swift annotated with the @propertyWrapper attribute & property with name wrappedValue.
The wrappedValue get and set block is where you can intercept and perform the operation you desire.
To use the property wrapper we will prefix the variable with @<Property Wrapper Name>
How does property wrapper internally work?
Every time the compiler encounters a property wrapper, it generates code that provides storage for the property wrapper and access to the property through the property wrapper.
The structure, enumeration, or class defines the property, but it is the property wrapper that provides storage for the property. As the name suggests, the property is wrapped by the property wrapper.
Accessing the wrapped value
Let’s see some code 👀
We are going to implement a Base64Encoding property wrapper which will accept a normal value as a String and when we read that value it will always return a Base64 Encoded String.
Let’s understand Base64Encoding
property wrapper implementation. We have a private variable property of String type & a wrapped value with a getter & setter.
The getter returns the value stored in the value
property by encoding it to base64EncodingString(). The setter assigns the newValue to the value
property. The wrappedValue
computed property acts as a proxy for the private value
property.
To use the Base64Encoding property wrapper, we just need to annotate the text property with @Base64Encoding
attribute.
Voilà! It is done. 💃🏻
Now whenever we access the text from the Payload, it will always return a base64 encoded string.
Projecting a Value from Property Wrapper
A property wrapper can expose additional functionality by defining a projected value.
The name of the projected value is the same as the wrapped value, except it begins with a dollar sign ($).
Let’s visualize this by using an example:
In the OddOrEven example above, the code adds a projectedValue property to the OddOrEven structure to track the number whether it is even or odd.
numberStruct.$someNumber accesses the wrapper’s projected value.
The value of numberStruct.$someNumber is true as the stored number is 6 which is even & the printed message is “Number is even”. However, the projected value is false as the stored number is 7 which is odd & the printed message is “Number is odd”.
A property wrapper can return a value of any type as its projected value. Here, projectedValue is of Bool data type.
Access to the Property Wrapper itself
To access the property wrapper type itself, you need to add an underscore ( _ ) to the property.
Here, we are adding additional functionality to check the numbers less than 10.
Here, _someNumber is providing access to the OddOrEven wrapper, so we can call the checkLessThanTen() method.
However, calling the wrapper from outside will generate a compilation error.
However, with the projectedValue the checkLessThanTen method can be exposed.
By adding the syntactic sugar dollar sign($) to the property, the wrapper’s instance can be exposed.
A wrapper can return self to expose the instance of the wrapper as its projected value.
projectedValue of type OddOrEven is returning self to expose the instance of OddOrEven wrapper.
Now, the wrapper can be called from outside & checkLessThanTen method call is successful.
Conclusion
Simply, we can use the property wrappers when we need to constrain the values of the properties to apply certain logic or functionality.
Property wrapper can apply to a local stored variable, but not to a global variable or a computed variable.
Property wrappers can be incredibly useful to reduce code duplication, improve readability, and create expressive APIs that facilitate code reuse.
References:
https://docs.swift.org/swift-book/LanguageGuide/Properties.html