Working With Property Wrappers

Anatomy of a Property Wrapper

Working With Property Wrappers
1 Anatomy of a Property Wrapper 08:19
2 Initial Values and Projected Values Plus 08:10
Download Your Free Copy of
The Missing Manual
for Swift Development

The Guide I Wish I Had When I Started Out

Join 20,000+ Developers Learning About Swift Development

Download Your Free Copy

Property wrappers were introduced in Swift 5.1 to eliminate boilerplate code, facilitate code reuse, and enable more expressive APIs. You may have noticed that SwiftUI and Combine make heavy use of property wrappers. Property wrappers are completely optional. You can write Swift without property wrappers. But once you become familiar with their benefits and understand how they work, you will understand why the Swift community is so excited about this addition to the language.

It took Swift's core team several revisions to come to a proposal everyone was happy with. Property wrappers were originally named property delegates, inspired by Kotlin's delegated properties. The idea is to easily and transparently apply common patterns to properties. Property wrappers eliminate boilerplate code and allow developers to write code that is more readable and more expressive.

Writing Boilerplate Code

I want to start with a simple example to explore the anatomy of a property wrapper. Fire up Xcode and create a playground by choosing the Blank template from the iOS > Playground section. Remove the contents of the playground and add an import statement for Foundation.

We define a struct with name Book. It defines a variable property, title, of type String.

import Foundation

struct Book {

    // MARK: - Properties

    var title: String

}

We want to make sure the value of title is always capitalized, regardless of the value that is assigned to title. We can enforce this policy by turning title into a computed property and declare a private property, _title, of type String. The computed property acts as a proxy for the private property. The getter of the computed property returns the value of the private property. In the setter of the computed property, the new value is capitalized and assigned to the private property.

import Foundation

struct Book {

    // MARK: - Properties

    private var _title: String

    var title: String {
        get { _title }
        set { _title = newValue.capitalized }
    }

}

Notice that access to the private property goes through the computed property. This is a convenient pattern and it is common. The problem is that we need to write boilerplate code every time we need to apply this pattern. This is the problem property wrappers solve. Take a close look at this example before continuing.

Anatomy of a Property Wrapper

Once you understand the ins and outs of property wrappers, you will understand that property wrappers aren't magical. A property wrapper is nothing more than an object that encapsulates a property. It controls access to the property it wraps.

A property wrapper is nothing more than a struct or class annotated with the propertyWrapper attribute and a property with name wrappedValue. These are the only requirements a property wrapper needs to meet. As the name implies, the wrappedValue property stores the value that is wrapped by the property wrapper.

Let's put this into practice. We create a property wrapper with name Capitalized. It capitalizes the value it manages. We define a struct with name Capitalized and annotate it with the propertyWrapper attribute. We also define a property, wrappedValue, of type String.

import Foundation

@propertyWrapper
struct Capitalized {

    // MARK: - Properties

    var wrappedValue: String

}

The Capitalized struct is a valid property wrapper, but it doesn't do anything just yet. We fix that in a moment. Let's put the property wrapper to use in the Book struct.

Applying the property wrapper to a property is as simple as annotating the property with the Capitalized attribute. Because the property wrapper controls access to the wrapped property, we no longer need to declare _title privately and we can remove the title computed property. Let's clean up the implementation by renaming _title to title.

struct Book {

    // MARK: - Properties

    @Capitalized var title: String

}

We can create a Book object using the memberwise initializer that is automatically generated for the Book struct. Even though title is a wrapped property, we can access it as if it were a regular property.

var book = Book(title: "the da vinci code")
book.title  // "The Da Vinci Code"

Adding Functionality to a Property Wrapper

Let's complete the implementation of the Capitalized property wrapper. We define a private, variable property, value, of type String to store the string the property wrapper manages.

import Foundation

@propertyWrapper
struct Capitalized {

    // MARK: - Properties

    private var value: String

    // MARK: -

    var wrappedValue: String

}

We also define a getter and a setter for wrappedValue. The getter returns the value stored in the value property. The setter capitalizes the new value and assigns the result to the value property. Notice that wrappedValue is no longer a stored property. It is a computed property that controls access to the private value property. The wrappedValue computed property acts as a proxy for the private value property.

import Foundation

@propertyWrapper
struct Capitalized {

    // MARK: - Properties

    private var value: String

    // MARK: -

    var wrappedValue: String {
        get {
            value
        }
        set {
            value = newValue.capitalized
        }
    }

}

Before we take a look at the result, we need to implement an initializer to assign an initial value to the value property. The initializer defines one parameter, wrappedValue, of type String. In the initializer, we capitalize the value stored in wrappedValue and assign the result to the value property.

import Foundation

@propertyWrapper
struct Capitalized {

    // MARK: - Properties

    private var value: String

    // MARK: -

    var wrappedValue: String {
        get {
            value
        }
        set {
            value = newValue.capitalized
        }
    }

    // MARK: - Initialization

    init(wrappedValue: String) {
        value = wrappedValue.capitalized
    }

}

It's time to revisit the Book object we created earlier. Because the Capitalized property wrapper is applied to the title property, the value of title is automatically capitalized.

This simple example illustrates the potential of property wrappers. We can apply the Capitalized property wrapper to any property of type String. The property wrapper ensures the property declaration is concise and expressive. The property wrapper provides storage for the property and defines the policy for the property.

How Does a Property Wrapper Work?

I'm sure you are familiar with Swift's lazy keyword to lazily instantiate objects. Even though the lazy instantiation of objects is built into the compiler, the concept is similar to property wrappers. In fact, you could replicate the functionality of the lazy keyword with a property wrapper.

Every time the compiler encounters a property wrapper, it synthesizes code that provides storage for the property wrapper and access to the property through the property wrapper. The struct 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.

Let's revisit the implementation of the Book struct. This is the code the compiler generates if you apply the Capitalized property wrapper to a property. The example illustrates what happens under the hood if you apply a property wrapper to a property. Notice that the property wrapper provides storage for the property. The property is converted into a computed property and access is controlled by the property wrapper.

struct Book {

    // MARK: - Properties

    // @Capitalized var title: String = "title"

    private var _title = Capitalized(wrappedValue: "title")

    var title: String {
        get {
            _title.wrappedValue
        }
        set {
            _title.wrappedValue = newValue
        }
    }

}

Whenever the compiler encounters a property wrapper, it generates code for you. The property wrapper syntax keeps your code clean and readable. Property wrappers allow you to document semantics at the point of definition. The previous example illustrates that the code that drives your application is more verbose and less pretty.

What's Next?

Property wrappers can be incredibly useful to reduce code duplication, improve readability, and create expressive APIs. I hope this episode has shown you that property wrappers are not magical. The compiler inspects the property wrappers you assign to properties and generates code that you don't need to write. There is no magic involved. In the next episode, we explore a few additional features of property wrappers.

Download Your Free Copy of
The Missing Manual
for Swift Development

The Guide I Wish I Had When I Started Out

Join 20,000+ Developers Learning About Swift Development

Download Your Free Copy
Next Episode "Initial Values and Projected Values"