Property Wrappers in Swift explained with code examples

Property Wrappers in Swift allow you to extract common logic in a distinct wrapper object. This new technique appeared at WWDC 2019 and first became available in Swift 5. It’s a neat addition to the Swift library that allows removing much boilerplate code, which we probably all have written in our projects.

You can find a background story on property wrappers on the Swift forums for SE-0258.  While the motivation is mainly talking about property wrappers being a solution for @NSCopying properties, there’s a typical pattern it solves you might all recognize.

What is a Property Wrapper?

You can see a property wrapper as an extra layer that defines how a property is stored or computed on reading. It’s beneficial for replacing repetitive code found in getters and setters of properties.

A typical example is custom-defined user defaults properties in which a custom getter and setter transform the value accordingly. An example implementation could look as follows, for which I’ll share implementation details later:

extension UserDefaults {

    @UserDefault(key: "has_seen_app_introduction", defaultValue: false)
    static var hasSeenAppIntroduction: Bool
}

The @UserDefault statement is a call into the property wrapper. As you can see, we can give it a few parameters that are used to configure the property wrapper. There are several ways to interact with a property wrapper, like using the wrapped value and the projected value. You can also set up a property wrapper with injected properties, which we’ll all cover later. Let’s first dive into the example of a User Defaults property wrapper.

Property wrappers and UserDefaults

The following code shows a pattern you all might recognize. It creates a wrapper around the UserDefaults object to make properties accessible without having to paste the string keys everywhere throughout your project.

extension UserDefaults {

    public enum Keys {
        static let hasSeenAppIntroduction = "has_seen_app_introduction"
    }

    /// Indicates whether or not the user has seen the onboarding.
    var hasSeenAppIntroduction: Bool {
        set {
            set(newValue, forKey: Keys.hasSeenAppIntroduction)
        }
        get {
            return bool(forKey: Keys.hasSeenAppIntroduction)
        }
    }
}

It allows you to set and get values from the user defaults from anywhere as follows:

UserDefaults.standard.hasSeenAppIntroduction = true

guard !UserDefaults.standard.hasSeenAppIntroduction else { return }
showAppIntroduction()

Now, as this seems to be a great solution, it could quickly end up being a large file with many defined keys and properties. The code is repetitive and asks for a way to make this easier. A custom property wrapper using the @propertyWrapper keyword can help us solve this problem.

How to create a property wrapper

Taking the above example, we can rewrite the code and remove a lot of overhead. For that, we have to create a new property wrapper which we will call UserDefault. Doing so eventually allows us to define a property as being a user default property.

If you’re using SwiftUI, you might want to use the AppStorage property wrapper instead. Take this just as an example of replacing repetitive code.

You can create a Property Wrapper by defining a struct and marking it with the @propertyWrapper attribute. The attribute will require you to add a wrappedValue property to provide a return value on the implementation level.

@propertyWrapper
struct UserDefault<Value> {
    let key: String
    let defaultValue: Value
    var container: UserDefaults = .standard

    var wrappedValue: Value {
        get {
            return container.object(forKey: key) as? Value ?? defaultValue
        }
        set {
            container.set(newValue, forKey: key)
        }
    }
}

As shown in the above example, the user defaults wrapper allows passing in a default value if there’s no registered value yet. We can pass in any value since the wrapper is using a generic value Value.

We can now change our previous code implementation and create the following extension on the UserDefaults type:

extension UserDefaults {

    @UserDefault(key: "has_seen_app_introduction", defaultValue: false)
    static var hasSeenAppIntroduction: Bool
}

As you can see, we can use the default generated struct initializer from the defined property wrapper. We pass in the same key as we used before, and we set the default value to false. Using this new property is simple:

UserDefaults.hasSeenAppIntroduction = false
print(UserDefaults.hasSeenAppIntroduction) // Prints: false
UserDefaults.hasSeenAppIntroduction = true
print(UserDefaults.hasSeenAppIntroduction) // Prints: true

In some cases, you might want to define your custom user defaults. For example, in cases where you have an app group defined user defaults. Our illustrated wrapper defaults to the standard user defaults, but you can override this to use your container:

extension UserDefaults {
    static let groupUserDefaults = UserDefaults(suiteName: "group.com.swiftlee.app")!

    @UserDefault(key: "has_seen_app_introduction", defaultValue: false, container: .groupUserDefaults)
    static var hasSeenAppIntroduction: Bool
}

Adding more properties using the same wrapper

Unlike the old solution, it’s effortless to add more properties when using a property wrapper. We can reuse the defined wrapper and instantiate as many properties as we need.

extension UserDefaults {

    @UserDefault(key: "has_seen_app_introduction", defaultValue: false)
    static var hasSeenAppIntroduction: Bool

    @UserDefault(key: "username", defaultValue: "Antoine van der Lee")
    static var username: String

    @UserDefault(key: "year_of_birth", defaultValue: 1990)
    static var yearOfBirth: Int
}

As you can see, the wrapper works with any type you define as long as the type is supported to be saved in the user defaults as well.

Storing optionals using a User Defaults Property Wrapper

A common issue you can run into when using property wrappers is that the generic value either allows you to define all optionals or all unwrapped values. There’s a common technique found in the community to deal with this, which makes use of a custom-defined AnyOptional protocol:

/// Allows to match for optionals with generics that are defined as non-optional.
public protocol AnyOptional {
    /// Returns `true` if `nil`, otherwise `false`.
    var isNil: Bool { get }
}
extension Optional: AnyOptional {
    public var isNil: Bool { self == nil }
}

We can extend our UserDefault property wrapper to conform to this protocol:

extension UserDefault where Value: ExpressibleByNilLiteral {
    
    /// Creates a new User Defaults property wrapper for the given key.
    /// - Parameters:
    ///   - key: The key to use with the user defaults store.
    init(key: String, _ container: UserDefaults = .standard) {
        self.init(key: key, defaultValue: nil, container: container)
    }
}

This extension creates an additional initializer which removes the requirement of defining a default value and allows working with optionals.

Lastly, we need to adjust our wrapper value setter to allow removing objects from user defaults:

@propertyWrapper
struct UserDefault<Value> {
    let key: String
    let defaultValue: Value
    var container: UserDefaults = .standard

    var wrappedValue: Value {
        get {
            return container.object(forKey: key) as? Value ?? defaultValue
        }
        set {
            // Check whether we're dealing with an optional and remove the object if the new value is nil.
            if let optional = newValue as? AnyOptional, optional.isNil {
                container.removeObject(forKey: key)
            } else {
                container.set(newValue, forKey: key)
            }
        }
    }

    var projectedValue: Bool {
        return true
    }
}

This now allows us to define optionals and set values to nil:

extension UserDefaults {

    @UserDefault(key: "year_of_birth")
    static var yearOfBirth: Int?
}

UserDefaults.yearOfBirth = 1990
print(UserDefaults.yearOfBirth) // Prints: 1990
UserDefaults.yearOfBirth = nil
print(UserDefaults.yearOfBirth) // Prints: nil

Great! We can kind of handle all scenarios now with the user defaults wrapper. The last thing to add is a projected value which we can convert to a Combine publisher, just like the @Published property wrapper.

Projecting a Value From a Property Wrapper

Property wrappers can add another property besides the wrapped value, which is called the projected value. This allows us to project another value based on the wrapped value. A typical example is to define a Combine publisher so that we can observe changes when they occur.

To do this with our user defaults property wrapper, we have to add a publisher, which will be a passthrough subject. It’s all in the name: it will simply pass through value changes. The implementation looks as follows:

 import Combine
 
 @propertyWrapper
 struct UserDefault<Value> {
     let key: String
     let defaultValue: Value
     var container: UserDefaults = .standard
     private let publisher = PassthroughSubject<Value, Never>()
     
     var wrappedValue: Value {
         get {
             return container.object(forKey: key) as? Value ?? defaultValue
         }
         set {
             // Check whether we're dealing with an optional and remove the object if the new value is nil.
             if let optional = newValue as? AnyOptional, optional.isNil {
                 container.removeObject(forKey: key)
             } else {
                 container.set(newValue, forKey: key)
             }
             publisher.send(newValue)
         }
     }

     var projectedValue: AnyPublisher<Value, Never> {
         return publisher.eraseToAnyPublisher()
     }
 } 

We can now start observing changes to our property as follows:

 let subscription = UserDefaults.$username.sink { username in
     print("New username: \(username)")
 }
 UserDefaults.username = "Test"
 // Prints: New username: Test 

This is great! It allows us to respond to any changes. As we defined our property statically before, this publisher will now work across our app. If you like to learn more about Combine, make sure to check out my article Getting started with the Combine framework in Swift.

Accessing private defined properties

Although it’s not recommended to work with property wrappers this way, it can be helpful in some cases to read the defined properties of a wrapper. I’m just going to demonstrate that this is possible, but you might want to rethink your code implementation if you need to access private properties.

A private defined property can be accessed by using an underscore prefix. This allows us to access the private property key from our user defaults property wrapper:

extension UserDefaults {
    static func printKey() {
        print(_yearOfBirth.key) // Prints "year_of_birth"
    }
}

Take it with a grain of salt and see whether you can solve your needs by using a different instance type instead. One idea could be to access the property wrapper’s enclosing instance, which will have the added benefit of accessing private defined properties from external framework modules.

A better alternative for accessing all property wrapper variables

By using the projected value of a property wrapper, we can define public access to all defined properties on a wrapper. We can do this by returning the wrapper instance itself as follows:

@propertyWrapper
struct UserDefault<Value> {
    let key: String
    let defaultValue: Value
    var container: UserDefaults = .standard

    var wrappedValue: Value {
        get {
            return container.object(forKey: key) as? Value ?? defaultValue
        }
        set {
            container.set(newValue, forKey: key)
        }
    }

    var projectedValue: UserDefault<Value> {
        self
    }
}

The projected value is now returning the user default structure with the generic type value. Its usage looks as follows:

extension UserDefaults {
    static func printPrivateProperties() {
        // Through underscore
        print(_hasSeenAppIntroduction.key)

        // Through projected value
        print($hasSeenAppIntroduction.key)
    }
}

Accessing a property wrapper’s enclosing instance

Using a custom static subscript, you’ll be able to access the property wrapper’s enclosing instance that defined the specific property wrapper. This can lead to exciting use-cases in which you can share the same underlying instance properties for each defined wrapper.

For example, we could define a preferences class that represents a single user defaults container:

final class Preferences {
    let container = UserDefaults(suiteName: "group.com.swiftlee.app")!

    @UserDefault(key: "has_seen_app_introduction", defaultValue: false)
    var hasSeenAppIntroduction: Bool
}

As you can see, we no longer defined the container in our user default property wrapper initializer. To still give access to the correct container, we can add a static subscript to our wrapper definition:

struct UserDefault<Value> {
    let key: String
    let defaultValue: Value

    @available(*, unavailable)
    var wrappedValue: Value {
        get { fatalError("This wrapper only works on instance properties of classes") }
        set { fatalError("This wrapper only works on instance properties of classes") }
    }

    static subscript(
        _enclosingInstance instance: Preferences,
        wrapped wrappedKeyPath: ReferenceWritableKeyPath<Preferences, Value>,
        storage storageKeyPath: ReferenceWritableKeyPath<Preferences, Self>
    ) -> Value {
        get {
            let propertyWrapper = instance[keyPath: storageKeyPath]
            let key = propertyWrapper.key
            let defaultValue = propertyWrapper.defaultValue
            return instance.container.object(forKey: key) as? Value ?? defaultValue
        }
        set {
            let propertyWrapper = instance[keyPath: storageKeyPath]
            let key = propertyWrapper.key
            instance.container.set(newValue, forKey: key)
        }
    }
}

This static subscript only works with instance properties of classes to have a reference type. By defining a fatal error inside the wrapper value, we ensure that usage on structures results in an exception. This technique is great for debugging purposes as it will directly provide feedback.

The static subscript allows us to access both the property wrapper container and the enclosing instance that defined the variable with the user defaults wrapper attribute. In other words, it gives us access to the UserDefault struct, as well as to the Preferences class.

By combining these, we can rewrite our user defaults wrapper to use the container as defined on the Preferences instance. This is a great way to reuse the same underlying user defaults container for each wrapper.

Attaching Property Wrappers to function and closure parameters

You can also use Property Wrappers with function or closure parameters, resulting in exciting use-cases that can help you remove more boilerplate code or improve debugging.

The following example demonstrates a wrapper for debugging purposes:

@propertyWrapper
struct Debuggable<Value> {
    private var value: Value
    private let description: String

    init(wrappedValue: Value, description: String = "") {
        print("Initialized '\(description)' with value \(wrappedValue)")
        self.value = wrappedValue
        self.description = description
    }

    var wrappedValue: Value {
        get {
            print("Accessing '\(description)', returning: \(value)")
            return value
        }
        set {
            print("Updating '\(description)', newValue: \(newValue)")
            value = newValue
        }
    }
}

A log will appear whenever the property is accessed or updated, while we also can add a breakpoint for enhanced debugging.

As an example, we could add this wrapper to the function argument duration in the following animation example:

func runAnimation(@Debuggable(description: "Duration") withDuration duration: Double) {
    UIView.animate(withDuration: duration) {
        // ..
    }
}

runAnimation(withDuration: 2.0)

// Prints:
// Initialized Duration with value 2.0
// Accessing 'Duration', returning: 2.0

This can be a great tool during debugging in which you add the wrapper temporarily. Obviously, this is just an example. Other common use-cases allow transforming a string value to uppercase or lowercase within a function argument.

Lastly, you can use the same wrapper within closures:

struct Article {
    let title: String
}

let articleFactory: (String) -> Article = { (@Debuggable title) in
    return Article(title: title)
}

let article = articleFactory("Property Wrappers in Swift")

// Prints:
// Initialized '' with value Property Wrappers in Swift
// Accessing '', returning: Property Wrappers in Swift

Unfortunately, at the moment of writing this article, we can’t make use of initializer properties when using the wrapper inside closures. This is likely a bug and might be fixed in a future update of Swift.

Other usage examples

Property wrappers are used throughout the default Swift APIs as well. Especially in SwiftUI, you’ll find property wrappers like @StateObject and @Binding. They all have something in common: making often used patterns easier to access.

Inspired by these built-in examples, you can start thing about creating your property wrappers like an autolayout enabled view:

final class MyViewController {
    @UsesAutoLayout
    var label = UILabel()
}

I’m often using this last example in my projects for views that are using auto-layout and require the translatesAutoresizingMaskIntoConstraints to be set to false. You can read more about this example in my blog post: Auto Layout in Swift: Writing Constraints Programmatically.

Defining Sample Files using a property wrapper

The prominent example focuses on user defaults, but what if you want to define another wrapper? Let’s dive into another example that will hopefully spark some ideas.

Take the following property wrapper in which we define a sample file:

@propertyWrapper
struct SampleFile {

    let fileName: String

    var wrappedValue: URL {
        let file = fileName.split(separator: ".").first!
        let fileExtension = fileName.split(separator: ".").last!
        let url = Bundle.main.url(forResource: String(file), withExtension: String(fileExtension))!
        return url
    }

    var projectedValue: String {
        return fileName
    }
}

We can use this wrapper to define our sample files which we might want to use for debugging or while running tests:

struct SampleFiles {
    @SampleFile(fileName: "sample-image.png")
    static var image: URL
}

The projectedValue property allows us to read out the file name as used in the property wrapper:

print(SampleFiles.image) // Prints: "../resources/sample-image.png"
print(SampleFiles.$image) // Prints: "sample-image.png"

This can be useful in cases you want to know which initial value(s) have been used by the wrapper to compute the final value. Note that we’re using the dollar sign here as a prefix to access the projected value.

Conclusion

Property wrappers are a great way to remove boilerplate in your code. The above example is only one of many scenarios in which it can be helpful. You can try it out yourself by finding repetitive code and replacing it with a custom wrapper.

If you like to learn more tips on Swift, check out the swift category page. Feel free to contact me or tweet me on Twitter if you have any additional suggestions or feedback.

Thanks!