Swift 5.1 introduces a new feature called Property Wrappers. For many iOS developers @ sign is mostly known in terms of Objective-C or InterfaceBuilder (e.g. @IBDesignable). Now it’s also related to regular properties in which you store some form of state. In many cases there’s logic behind modifying those states which is implemented inside our getters and setters. We might for example validate data, notify something or transform our views according to changes.
Property Wrappers can be really useful and give us a lot of opportunities for writing less code by reusing already written one. However, it also has cons and might be tricky sometimes but we’re going to talk about it later. First of all, let’s take a look at possibilities that this feature gives and try to understand how it works.
Understanding Property Wrappers
If you use SwiftUI property wrappers, you’re familiar with their properties, but let me explain them to newcomers.
Swift Programming Language documentation gives us an example where you can validate an integer type property to store value equals to twelve or less using this new feature. Let’s take a look on the code given in this example:
As you can see, we create a new property wrapper by defining a new struct with @propertWrapper attribute above definition. Inside those structs we have a private variable which stores our value, initialize method where you can assign value given in property initialization to this private variable and a wrappedValue variable which has defined get and set methods. Logic behind those methods are simple – in getter we just return our value, in setter we’re assigning new value only if it’s less than 12 – other way we assign 12.
Documentation also gives as an example how to use this newly defined wrapper:
We can apply our property wrapper by adding a wrapper’s name before (or above) our property definition. Also, this snippet shows us that if we assign value 10 to our property it’ll be assigned properly, but if we want to do the same with a bigger value, in this case 24 – it’ll be rejected and overwritten by 12.
Let’s extend this example by clamping this value in range 0-12 by creating a @Clamping property wrapper which takes another parameter in init method – the range which defines bounds to clamp our value inside them.
In this case we have to pass another parameter to our wrapper in the init method. We can pass this parameter like this:
This example might be useful when defining a Color struct to pass RGB values and clamp them in range 0…255.
Other uses of Property Wrappers and their possible cons
To not dive in too many examples we would like to introduce some other uses of discussed feature in the list below:
- Logging state changes in console / file
- Saving state changes inside UserDefaults or database
- Data validation
- Better codable
- Atomic property
But not looking through rose-coloured spectacles – property wrappers may also have some disadvantages:
- Value is stored always as a private variable
- There might be a hidden massive logic behind property wrapper
- Property wrappers can’t be aliased
- You cannot override property wrappers
- Property wrappers cannot be required in protocols
Property Wrappers might ease the pain when writing the same code twice and can save a lot of time. On the other hand, they can also be painful for new programmers who joined the project because they have to spend time learning to use them and study the ones we’ve already created. Property Wrappers may also hide a complex logic behind which can be hard to understand. However, there are many uses where this feature can be helpful and make our code much more readable.
Something special for the readers
I would like to share a GitHub URL to two libraries using the discussed feature as a kind of a prize for reading the whole article.
First one is called Resolver – this library allows us to inject our dependencies using Property Wrappers and it’s something I really appreciated in my last project. It’s fast and minimalistic.
Another library using property wrappers in a nice way is BetterCodable which, as its name says, makes using our codable structs easier.