Published on

Introduction to Property Wrappers in Swift

Swift 5.1 introduces a new property concept called property wrapper. A property wrapper helps you mark a clear distinction between the code that defines a property and the code that manages how that property is stored. For example, a common task that we as iOS developers face regularly is persisting data in the UserDefaults store. In this tutorial, we’ll learn how to handle reading and writing data from and to UserDeafaults. This can be streamlined and the distinction mentioned above can be created by using a property wrapper.

Getting started

Let us get started by creating a struct and by defining a generic type. (If you’re unfamiliar with generics in Swift, you can check out my article here.). This type will handle our UserDefault value’s type.

struct UserDefault<Value> {
    var key: String
    var defaultValue: Value   init(key: String, value: Value) {
       self.key = key
       self.defaultValue = value
    }
}

Nothing much is going on here. We just created two properties for this struct UserDefault (Please note that UserDefault and UserDefaults are two separate entities, the latter being the system reserved keyword for the UserDefaults key-value pair store). We then initialize this struct by providing an explicit initializer. (Swift will generate an auto member-wise initializer for a struct in-case an explicit init method isn’t provided.) To define a property wrapper, you make a structure, enumeration, or class that defines a wrappedValue property. So, we’ll create a new computed property inside of our UserDefault struct called wrappedValue. This property will have a getter and a setter.

The getter is where the property wrapper returns a value when accessed. In this case, we’d like to get the value from our UserDefault store by passing it the key from the struct. We’ll explicitly cast this returning value to type Value and use the nil coalescing operator to return the default value in case the UserDefault store didn’t return us a value (when the value for that key is nil). The setter does exactly what it sounds like and sets a newValue. This newValue is provided by the propertyWrapper method and we can use it directly inside of the setter for our computed property.

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

One last thing- To mark this struct as a property wrapper we simply put @propertyWrapper before the struct declaration. Your final UserDefault struct should now look like this-

@propertyWrapper
struct UserDefault<Value> {
    let key: String
    let defaultValue: Value    init(key: String, value: Value) {
        self.key = key
        self.defaultValue = value
    }    var wrappedValue: Value {
        get {
            return UserDefaults.standard.object(forKey: self.key) as? Value ?? defaultValue
        }
        set {
            UserDefaults.standard.setValue(newValue, forKey: self.key)
        }
    }
}

Usage

Property wrappers cannot currently be used in top-level code so we’ll create a helper struct that can handle the property wrapper for us. Create a struct called UserDefaultValue and add a new static var that is a representation of our propertyWrapper struct.

struct UserDefaultValues {
    @UserDefault(key: “isLoggedIn”, value: false) static var isLoggedIn: Bool
}

Here, we’re simply using the static keyword to not have to create an instance of this helper struct every time.

Result

This isLoggedIn property is wrapped by the UserDefault class which is our property wrapper. This means we can access the static property to access the UserDefault values for the key that we’ve provided directly. In case the value doesn’t exist it’ll return us the super handy default value.

print(UserDefaultValues.isLoggedIn) // prints false

This line of code will print out false since we haven’t set a value in the UserDefaults Store for the key isLoggedIn and therefore it resorts to the default value which is false.

To set a value we’ll just set a new value to this static var.

UserDefaultValues.isLoggedIn = true

If we print out the static var now we’ll see that the value in the store for the key has changed successfully.

print(UserDefaultValues.isLoggedIn) //prints true