Property Wrappers were introduced to Swift in 5.1. Initially, they can seem a bit mystifying. However, they’re a powerful tool, helping to streamline your code and make it more expressive. Today, we’ll demystify Property Wrappers and learn how to use them in Swift.

What Exactly is a Property Wrapper?

Think of a Property Wrapper as a special kind of structure, class, or enumeration that “wraps” around a property in your code. This wrapper can add extra behaviour to the property, making it easier to manage and modify.

In Swift, a Property Wrapper is identified by the @propertyWrapper annotation. Here’s a straightforward example:

import Foundation

@propertyWrapper
struct Capitalized {
    private var value: String = ""

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

struct User {
    @Capitalized var firstName: String
    @Capitalized var lastName: String
}

var user = User()
user.firstName = "john" // John
user.lastName = "doe" // Doe

In this simple case, the Capitalized property wrapper ensures that the firstName and lastName properties always store their value with the first letter capitalized.

When Should You Use Property Wrappers?

Property Wrappers are handy when there’s a certain behaviour you want to consistently apply to a property. They can help manage repeated code patterns, such as data validation, transformation of data, or even more complex behaviours like making a property thread-safe or enabling lazy initialization.

If you find yourself writing the same code around properties multiple times, a Property Wrapper might be a good way to encapsulate that common behaviour.

Digging Deeper: An Example with Atomic

Let’s dig a bit deeper into Property Wrappers with an example that ensures a property is accessed in a thread-safe manner. If you’re new to concurrency and thread-safety, don’t worry too much about the details - just know that when multiple threads access the same data, we need to ensure they don’t interfere with each other. Without Property Wrappers, managing this can require complex code.

With Property Wrappers, however, the thread safety logic can be tucked away neatly:

import Foundation

@propertyWrapper
struct Atomic<Value> {
    private var value: Value
    private let lock = NSLock()

    init(wrappedValue value: Value) {
        self.value = value
    }

    var wrappedValue: Value {
        get {
            lock.lock()
            defer { lock.unlock() }
            return value
        }
        set {
            lock.lock()
            defer { lock.unlock() }
            value = newValue
        }
    }
}

class MyThreadSafeClass {
    @Atomic var counter = 0
}

let myObject = MyThreadSafeClass()

DispatchQueue.global().async {
    for _ in 0..<1000 {
        myObject.counter += 1
    }
}

DispatchQueue.global().async {
    for _ in 0..<1000 {
        myObject.counter += 1
    }
}

In this example, the Atomic Property Wrapper ensures that accesses to the counter property are thread-safe, making the code using this property simpler and cleaner.

As an example, Signal are using a similar construct in their open-source code.

Wrapping Up 🤣

Swift Property Wrappers can make your code more elegant and less prone to repeated patterns, but it’s important to use them wisely. As with any tool, understanding their purpose and how they work is key.!