UserDefaults and Property Wrappers in Swift

andresr173

Andres Rojas

Posted on April 6, 2020

UserDefaults and Property Wrappers in Swift

The easiest way to save your objects into UserDefaults


According to Apple's documentation,

UserDefaults is an interface to the user’s defaults database, where you store key-value pairs persistently across launches of your app.

The UserDefaults class provides convenience methods for accessing common types such as floats, doubles, integers, Boolean values, and URLs. If you want to store any other type of object, you should typically archive it to create an instance of NSData.

Well, if we need NSData, how does it work?

First of all, we need to implement Codable, this is the easiest way to encode/decode our structs.

Let's define our Struct.

struct Task: Codable {
    var title: String
    var isDone: Bool
}
Enter fullscreen mode Exit fullscreen mode

Before start using PropertyWrappers I'll show you use UserDefaults to save and get our objects.

func saveTask(_ task: Task, forKey key: String) throws {
    let userDefaults = UserDefaults.standard
    let encoder = JSONEncoder()
    do {
        let data = try encoder.encode(task)
        userDefaults.set(data, forKey: key)
        userDefaults.synchronize()
    } catch {
        throw(Errors.encodeError)
    }
}

func getTask(forKey key: String) throws -> Task? {
    let userDefaults = UserDefaults.standard
    let decoder = JSONDecoder()
    guard let data = userDefaults.object(forKey: key) as? Data else     {
        throw(Errors.noUserDefaults)
    }
    do {
        let task = try decoder.decode(Task.self, from: data)
        return task
    } catch {
        throw(Errors.decodeError)
    }
}
Enter fullscreen mode Exit fullscreen mode

As you can see we have declared two different functions:

  • save Task, which encodes our Task model and use UserDefaults to save the whole data.
  • getTask, which uses UserDafaults to get the data and decodes it to get the Task model.

This is pretty simple to use, but the problem comes when we need to get and save our objects several times, so we need to be aware to call every function at a specific time.

What can we do to improve this process? Property Wrappers to the rescue!

A property wrapper is a swift feature that allows us to define a custom type that implements get and set methods, and we can reuse it everywhere in the app.

Before starting implementing our property wrapper, there are two important things to take into account:

  • Be sure you are using the @propertyWreapper annotation
  • You need to use a property called wrappedValue

This property will implement the get and set methods and here is where we're going to add our code to use the UserDefaults.

@propertyWrapper
struct UserDefaultsWrapper<Value: Codable> {
    let key: String
    let defaultValue: Value
    let userDefaults = UserDefaults.standard

    var wrappedValue: Value {
        get {
            let data = userDefaults.data(forKey: key)
            let value = data.flatMap { try? JSONDecoder().decode(Value.self, from: $0) }
            return value ?? defaultValue
        }

        set {
            let data = try? JSONEncoder().encode(newValue)
            userDefaults.set(data, forKey: key)
            userDefaults.synchronize()
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

This property wrapper expects two parameters: key and defaultValue, we use the default value when the UserDefaults can't retrieve the data, this will happen the first time when we try to access the property.

Finally, the implementation looks like this:

let defaultTasks: [Task] = [
    Task(title: "Learn SwiftUI", isDone: false),
    Task(title: "Share this article", isDone: true)
]
struct TaskProvider {
    @UserDefaultsWrapper(key: "Tasks", defaultValue: defaultTasks)
    var tasks: [Task]
}
print(TaskProvider().tasks)
Enter fullscreen mode Exit fullscreen mode

And the result will be:

2 elements
  ▿ __lldb_expr_4.Task
    - title: "Learn SwiftUI"
    - isDone: false
  ▿ __lldb_expr_4.Task
    - title: "Share this article"
    - isDone: true
Enter fullscreen mode Exit fullscreen mode

If you want to check the full implementation please visit this Gist

💖 💪 🙅 🚩
andresr173
Andres Rojas

Posted on April 6, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related