How can Property Wrappers and Function Builders be leveraged?

Vincent Pradeilles
Jun 27, 2019 · 5 min read
Image for post
Image for post
Thanks to Lukas for the illustration

During WWDC 2019, Apple has unveiled an incredible amount of exciting new pieces of technology. Among them was a new release of the Swift language: Swift 5.1.

Don’t be fooled by its minor version number: this increment is actually packed with new features that will enable a whole new range of syntaxes within our codebases.

In this article, I want to show you examples of how two of those new features, Property Wrappers, and Function Builders, can be effectively implemented and leveraged.

Property Wrappers

If you’ve looked at some code samples from SwiftUI, you might have wondered at the fact that some properties are decorated with attributes such as @State or @EnvironmentObject.

Those attributes are what Swift 5.1 calls Property Wrappers. The idea behind them is pretty simple: a Property Wrapper encapsulates a behavior, that will be triggered whenever the getter and setter of the property are called.

To better understand how they work, and how we can build our own Property Wrappers, let’s have look at an example.

Imagine that our code needs to deal with values that have an expiration date: whenever we try to access such values, we first need to check that it hasn’t expired. To do so, we are going to implement a Property Wrapper that we’ll call @Expirable.

First, we’re going to declare our wrapper as a generic struct, and we'll decorate it with the attribute @propertyWrapper:

The attribute @propertyWrapper lets the compiler know that we intend to use this struct as a Property Wrapper.

Then, we’re going to store some data within the struct, beginning with the lifetime and expiration date of the value. We’ll also implement a helper function, to the computer whether the value has expired:

After that, the next step is to satisfy the API required by a Property Wrapper. This API consists of a single property var value: Value. We'll implement it as a computed property, and within its getter and setter, we'll perform the logic that manages the expiration date:

As you can see, the actual value is stored within a private property innerValue, and its value is only returned if the associated expiration date has not yet been reached. You can also note that we added the constraint that the generic type Value must conform to ExpressibleByNilLiteral: this will ensure that this Property Wrapper can only be used to decorate optional properties.

And now, the time has finally come for us to use our new custom attribute @Expirable 🎉

If we try and test how this struct behaves, we'll see that, indeed, through the attribute @Expirable, the expiration date associated with the value is managed in a way that is completely transparent to the programmer.

Function Builders

Once again, if you’ve looked at SwiftUI, there’s a good chance that you’ve stumbled across code such as this:

If you’re a seasoned Swift developer, you’ve definitely been wondering “Wait, how does this HStack thing actually get built?”.

We can see that its initializer takes a trailing closure, but this closure seems to be returning two values Text("SwiftUI") and Text("rocks"), and that's just not how Swift closures work 🤔

As we might expect, there is no black magic at work, but rather the use of another new feature of Swift 5.1: Function Builders.

The idea behind Function Builders can be a little bit hard to grasp at first, but don’t worry: once you see it in action, it will all become clear.

Basically, Function Builders allow you to write functions inside which every top-level expressions are collected and merged together, resulting in a return value that aggregates them all.

To see how it works under the hood, we are going to implement a custom syntax that will allow us to write assertions using KeyPaths, as follows:

To begin, we’ll start by implementing a type that implements the data we want to aggregate, in our example it’s a type that encapsulates an assertion:

Then we’ll add to this type the ability to be combined with another assertion. The logic behind it is pretty trivial: we just no need to execute both assertions one after the other.

Finally, we’ll define a special value, that will represent an empty assertion. It will prove useful when we need to combine together a collection of assertion, as it will be the ideal candidate for an initial value — if you enjoy algebra, you might recognize that the type Assertion actually implements a Monoid 🤓.

Then we need a way to build an Assertion through the use of the operator ==. Fortunately, the way Swift deals with operators make it very easy to implement:

Now, we are almost there, the only thing left to do is to actually implement the Function Builder that will collect and combine our assertions. To do so, we first need to declare a new type and mark it with the attribute @_functionBuilder:

Then, inside this type we need to write a static method that will implement how a collection of assertions should be aggregated into a single value:

As you can see, the code is pretty straightforward: we use reduce(_:_:) along with our combine(with:) method to merge a collection of assertions into a single value.

Now we are almost done, there is just one more thing left to do: implement the function assert(), that will make use of the Function Builder we have just created:

As you can see, the attribute @AssertBuilder is used to decorate the closure our function takes as a parameter. This indicates to the compiler that every top-level expression of type Assertion within the closure must be collected, and aggregated together using the implementation of the Function Builder.

This is it, everything we needed has been implemented: we can now write assertions using our goal syntax, and it will build and run as we intended 🎉

To conclude

As we’ve seen, Swift 5.1 delivered us two exciting new features: Property Wrappers and Function Builders. Together, they open the way to a whole new range of syntaxes that used to be impossible to implement.

However, be careful! Custom syntaxes share a common trait with custom operators: while they can deliver powerful features, they also notably increase the complexity of a codebase and make our code less predictable.

In this respect, when pondering whether you should be building your own Property Wrapper or Function Wrapper, you can ask yourself the following question:

Is your new custom syntax going to be used consistently across a large part of your codebase?

If you feel that the answer is no, then chances are you shouldn’t introduce a new syntax, and instead focus on implementing the feature you need through standard Swift syntax. On the other hand, if the answer is yes, then, by all means, go ahead!

You liked this article and you want to see more content like it? Feel free to follow me on Twitter: https://twitter.com/v_pradeilles

Image for post
Image for post

Flawless iOS

🍏 Community around iOS development, mobile design, and…

Vincent Pradeilles

Written by

French iOS software engineer, working in Lyon, France 🇫🇷 https://twitter.com/v_pradeilles

Flawless iOS

🍏 Community around iOS development, mobile design, and marketing

Vincent Pradeilles

Written by

French iOS software engineer, working in Lyon, France 🇫🇷 https://twitter.com/v_pradeilles

Flawless iOS

🍏 Community around iOS development, mobile design, and marketing

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store