Home Property Wrappers in Swift - (Must Use Feature)
Post
Cancel

Property Wrappers in Swift - (Must Use Feature)

Have you ever found yourself struggling with repetitive code while managing variables in Swift?
For instance, dealing with tasks like persistently saving a value or ensuring thread-safe access to a variable.
If yes then property wrapper can come to your rescue.”

Property wrappers, as the name suggests, are wrapper objects designed to contain common logic. Apple first introduced in the WWDC 2019 session with the Swift 5.1 version.

Do you Know
Swift property wrappers were originally named property delegates (SE-0258), inspired by Kotlin’s delegated properties.

Basics

A property wrapper is an object that adds an additional layer for reading and writing a variable. It is a class or struct annotated with the @propertyWrapper attribute and contains a property wrappedValue.

A typical use-case of a property wrapper is saving & accessing the value from persistent. Let’s create a property wrapper to save a bool value in UserDefault.

1
2
3
4
5
@propertyWrapper
struct UserDefaultPersist {
    var wrappedValue: Bool
    // your implementation to save a value in user default.
}

You can use the property wrapper by marking a property with the @UserDefaultPersist keyword.

1
@UserDefaultPersist var isCoachMarkShown: Bool = false

Here, property isCoachMarkShown controls the access of the wrapped property wrappedValue.

Compiler Generated Code

The Swift Compiler converts the above line of code into two properties, eliminating the need for direct access to the wrapped value.

1
2
3
4
5
6
7
8
//Compiler Generated code
var $isCoachMarkShown: UserDefaultPersist = UserDefaultPersist() //Property 1

//Property 2
public var isCoachMarkShown: Bool {
get {$isCoachMarkShown.wrappedValue}
set {$isCoachMarkShown.wrappedValue = newValue}
}

Adding functionality to the Property Wrapper

Let’s complete the implementation of UserDefault saving the bool value. To store a value in UserDefaults a key string is required and cannot be statically defined within a property wrapper. so let’s try with init of property wrapper:

1
2
3
4
5
6
7
8
9
@propertyWrapper
struct UserDefaultPersist {
    private var defaultKey: String
    var wrappedValue: Bool = false

    init(defaultKey: String) {
        self.defaultKey = defaultKey
    }
}

Now we have a key to store, let’s build a logic to store a value in UserDefault.

1
2
3
4
5
6
7
var wrappedValue: Bool {
    get {
        UserDefaults.standard.bool(forKey: defaultKey)
    } set {
        UserDefaults.standard.setValue(newValue, forKey: defaultKey)
    }
}

Now there’s a slight modification in applying the property wrapper to any property as init of the property wrapper is in the picture.

1
UserDefaultPersist(defaultKey: "isCoachMarkShown") var isCoachMarkShown: Bool

We need to pass defaultKey as parameter in init.

Final Code - UserDefault Bool

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@propertyWrapper
struct UserDefaultPersist {
    private var defaultKey: String
    var wrappedValue: Bool {
        get {
            UserDefaults.standard.bool(forKey: defaultKey)
        } set {
            UserDefaults.standard.setValue(newValue, forKey: defaultKey)
        }
    }
    
    init(defaultKey: String) {
        self.defaultKey = defaultKey
    }
}

More Examples

UserDefault(Generic)

The following example can be used to store any value in UserDefault.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@propertyWrapper
struct UserDefaultPersist<Value> {
    private var defaultKey: String
    var wrappedValue: Value? {
        get {
            UserDefaults.standard.value(forKey: defaultKey) as? Value
        } set {
            UserDefaults.standard.setValue(newValue, forKey: defaultKey)
        }
    }
    
    init(defaultKey: String, defaultValue: Value) {
        self.defaultKey = defaultKey
        self.wrappedValue = defaultValue
    }
}

Uses:

1
@UserDefaultPersist(defaultKey: "isCoachMarkShown", defaultValue: false) var isCoachMarkShown: Bool?

Atomic Property

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@propertyWrapper
struct AtomicPropertyWrapper<Value> {
    private let lock: NSLock = .init()
    private var value: Value
    var wrappedValue: Value {
        get { return getValueAtomicity() }
        set { setValueAtomicity(newValue: newValue) }
    }
    
    init(wrappedValue value: Value) {
        self.value = value
    }
    
    fileprivate func getValueAtomicity() -> Value {
        lock.lock()
        defer { lock.unlock() }
        return value
    }
    
    fileprivate mutating func setValueAtomicity(newValue: Value) {
        lock.lock()
        defer { lock.unlock() }
        value = newValue
    }
}

Uses:

1
@AtomicPropertyWrapper var counter: Int = 1

What’s Next?

Property wrapper can be a handy feature to remove boilerplate code & encourage code reusability. Apple also recommends using the feature to create expressive API. There is no magic here, the compiler generates a code for you to access the variable.

Thanks for reading! I’d love to hear your thoughts on this article :).

This post is licensed under CC BY 4.0 by the author.

SwiftUI - Observed vs State Object

Swift Dynamic Member Lookup

Comments powered by Disqus.