Simplifying Data Persistence with Property Wrappers in Swift

In modern software development, data persistence plays a crucial role in preserving and accessing user preferences and app state across sessions. With the release of Swift 5.1, a new feature called property wrappers was introduced, which allows developers to encapsulate and automate common behaviors associated with property access. In this article, we will explore how property wrappers can simplify data persistence using a practical example.


Code Example

Let’s consider a scenario where we want to persist various user settings in our app, such as the user’s name, preferred currency, color scheme, profile picture, and the ID of the latest order placed. We can leverage the power of property wrappers to achieve this in a concise and efficient manner.

Add a property wrapper

In this way we add a @Storable property to use in your application.

This code allows you to save your date in the UserDefault object.

@propertyWrapper
struct Storable<T: Codable> {
    private let key: String
    private let defaultValue: T
    
    init(key: String, defaultValue: T) {
        self.key = key
        self.defaultValue = defaultValue
    }
    
    var wrappedValue: T {
        get {
            guard let data = UserDefaults.standard.object(forKey: key) as? Data else {
                return defaultValue
            }
            let value = try? JSONDecoder().decode(T.self, from: data)
            return value ?? defaultValue
        }
        set {
            let data = try? JSONEncoder().encode(newValue)
            UserDefaults.standard.set(data, forKey: key)
        }
    }
}

Second step is to map in an Enum a list of items to store, example:

enum AppSettings {
    @Storable(key: "userName", defaultValue: "")
    static var userName: String

    @Storable(key: "currency", defaultValue: "EUR")
    static var currency: String?

    @Storable(key: "primaryColor", defaultValue: nil)
    static var primaryColor: String?
}

Explanation

In the given code snippet, we define a custom property wrapper called Storable. This wrapper encapsulates the logic for reading and writing values to the UserDefaults storage, while also providing a default value for each property.

To use the Storable wrapper, we define an enumeration called AppSettings. Inside this enumeration, we declare various properties using the @Storable attribute. Each property is associated with a unique key and a default value. For example, shownName represents the user’s displayed name, and its default value is an empty string.

The wrappedValue computed property within the Storable wrapper handles the actual reading and writing of the property value to the UserDefaults. When accessing the property, it checks if a value exists in the storage for the associated key. If a value is found, it is decoded using JSONDecoder and returned; otherwise, the default value is returned.

When assigning a value to the property, the wrappedValue setter encodes the new value using JSONEncoder and stores it in the UserDefaults for the corresponding key.

Utilizing the AppSettings enumeration, you can easily access and modify the stored values in a straightforward manner. Here’s an example of how you can use it in your code

Let’s use it with an example:


AppSettings.shownName = "John Doe"
print(AppSettings.shownName) // Output: "John Doe"

AppSettings.currency = "USD"
print(AppSettings.currency) // Output: "USD"

Now your value is shared and stored and can be read from everywhere.

You can also replace UserDefault with Keychain is you need something more secure or whatever you like.

By utilizing property wrappers, you eliminate the need for repetitive code for storing and retrieving values from persistent storage. The Storable wrapper handles the serialization and deserialization of the property values, making it easier to manage and persist user settings and app state.


Conclusion

Property wrappers offer a powerful way to simplify data persistence in Swift. By encapsulating the reading and writing logic within a reusable wrapper, developers can easily store and retrieve values from persistent storage without boilerplate code. The example code provided demonstrates how property wrappers, combined with the UserDefaults storage, can streamline the process of persisting user settings in an application.

For further reading on property wrappers and data persistence in Swift, you may refer to the following resources:

  1. Swift.org – Property Wrappers: https://docs.swift.org/swift-book/LanguageGuide/Properties.html#ID617
  2. Apple Developer Documentation – UserDefaults: https://developer.apple.com/documentation/foundation/userdefaults
  3. WWDC 2019 – Modern Swift API Design: https://developer.apple.com/videos/play/wwdc2019/415/
 

Alberto Pasca

Software engineer @ Pirelli & C. S.p.A. with a strong passion for mobile  development, security, and connected things.