Property Wrappers in Swift

Benjamin Dumont
Just-Tech-IT
Published in
4 min readMay 5, 2022

--

Swift Logo Icon on Iconscout

Like many other languages, Swift made possible for developers to use Property Wrappers (also called annotations in Java for instance) in order to reduce the implementation for boilerplate code and improve the code readability.

What is a property wrapper

A property wrapper defines how a property is stored or computed on reading. It applies on property, defined with the keyword “@propertyWrapper” and used with the name of the property wrapper preceded with a “@”.

Our first property wrapper

Let’s start by writing a property wrapper which uppercase a String. First we need to create this Wrapper as follow:

Each property wrapper must have a wrappedValue, which is defined by get, set or didSet methods. We use the init phase when a new object is initialized for this property wrapper and the didSet phase for each change of the variable.

And now we can implement our Property Wrapper:

You can notice that if we print the global object User, we have the information that lastname has been changed by the property wrapper.

Property Wrapper and Generics

As a property wrapper is defined by a struct or a class, it has the abilities of these elements. In order to reduce boilerplate code, you may want to introduce generics. In the previous example, we didn’t use generic since the uppercase method is only applicable on a String (the type is defined by the wrappedValue).

Now, we will use generics to store different kind of primitive types in the UserDefaults.

Now let’s create an object to store:

You can notice that in the property definition, you don’t have to set explicitly the type of the property since you tell it to the property wrapper. The empty init is here for the demonstration. One way to instanciate easily the object User2 is to create an init. If you don’t (and with this property wrapper definition), you will have compilation errors because the compiler wants a different type that you can see in the error message below:

Cannot convert value of type ‘Int’ to expected argument type ‘UserDefaultsStore<Int?>’

And now we create the User2 object:

Property wrapper on a property wrapper

This introduce some kind of code smell but you can use property wrapper on variables inside a property wrapper. For instance, if I combine the 2 previous example to uppercase the name of a user, I can do this:

Property wrapper and closure

Property wrappers only aply to properties/variables, so it’s not possible to use it on functions. But, if your architecture permits it, you can use on closures. The example below comes from this video:

So, we will create a property wrapper to run some code on main thread:

Here is our ViewModel:

Some part of the code is on background thread. But the closure updateUI will run on the main thread.

And now the UI code:

Property wrapper as argument

With Swift 5.5 we can now use property wrappers on function arguments.

Projected Value

Projected value is a way to access to the property wrapper itself. Why do we need this? Let’s reuse the UserDefaultStore property wrapper we saw before. This wrapper enable us to get the data (identified by a key) from the UserDefaults and to set the data (with persistence) into this UserDefaults. What if we need to remove the data (removing is different that setting it to nil)? I would like to be able to reuse this property wrapper (to use the DRY pattern).

First, we will create the remove function into the wrapper to do this:

The remove function is accessible through the property wrapper, so I can’t use it on the “name” property directly, since I have to reference the property wrapper. And here comes the projected value. To access to a property wrapper having a projected value, we have to use the “$” character:

SwiftUI and Property Wrappers

SwiftUI use a lot of property wrappers. State, EnvironmentObject, Binding and Published are some of them. Let’s focus on the State property wrapper and have a look the the code below:

@State lets us manipulate small amounts of value type data locally to a view. This owns its data.

@Binding refers to value type data owned by a different view. Changing the binding locally changes the remote data too. This does not own its data.

We see in the previous code that the wrapped value will get/set its value from a pointee (aka value being stored and pointed to by the pointer).

The projected value creates a binding for change purpose.

That’s the reason why we use our state variable, we prefix it with “$”, to use its projected value and to create a binding. Each change, thanks to the binding, will be transmitted to the pointee (thanks to the wrapped value).

Limitations

These are the limitations of property wrappers:

  • only applicable to var
  • only one property wrapper per property
  • no inheritance or protocol compliance
  • a property with a wrapper cannot be overridden in a subclass.
  • A property with a wrapper cannot be lazy, @NSCopying, @NSManaged, weak, or unowned.
  • A property with a wrapper cannot have custom set or get method.
  • wrappedValue, init(wrappedValue:) and projectedValue must have the same access control level as the wrapper type itself.
  • A property with a wrapper cannot be declared in a protocol or an extension.

References

--

--