Property Wrappers in Swift explained with code examples

Property Wrappers in Swift allow you to extract common logic in a distinct wrapper object. Since the introduction during WWDC 2019 and becoming available in Xcode 11 with Swift 5 there have been many examples shared across the community. It’s a neat addition to the Swift library that allows removing a lot of boilerplate code which we probably all have written in our projects.

A background story on property wrappers can be found 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 common pattern it solves you might all recognize.

What is a Property Wrapper?

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

A common example is custom-defined user defaults properties in which a custom getter and setter are used to transform the value accordingly. An example implementation could look as follows:

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 as well which 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 easily end up being a large file with a lot of 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.

Using property wrappers to remove boilerplate code

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. This will eventually allow 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.

@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)
        }
    }
}

The wrapper allows passing in a default value if there’s no registered value yet. We can pass in any value as the wrapper is defined with 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 own custom user defaults. For example, in cases where you have an app group defined user defaults. Our defined wrapper defaults to the standard user defaults but you can override this to use your own 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 very easy to add more properties when using a property wrapper. We can simply 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 initialiser 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 have the option to 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 common 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 passthrough 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.

Defining Sample Files using a property wrapper

The above example is heavily focused around user defaults, but what if you want to define another wrapper? Let’s dive into another example which 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.

Accessing private defined properties

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

In the above example, we could access the filename as well by using an underscore prefix. This allows us to access the private property filename:

extension SampleFiles {
    static func printKey() {
        print(_image.fileName)
    }
}

Take it with a grain of salt and see whether you can solve your needs by using a different instance type instead.

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 own property wrappers. Another idea could be to create a wrapper for command-line actions:

@Option(shorthand: "m", documentation: "Minimum value", defaultValue: 0)
var minimum: Int

Or for views that have their layouts defined in code:

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

This last example I’m using often 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.

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 useful. 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 to me on Twitter if you have any additional tips or feedback.

Thanks!