UserDefaults and Property Wrappers in Swift
Tags: swift, userdefaults, propertywrappers
Published: 2020-04-06 23:40:28

###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](https://developer.apple.com/documentation/swift/codable), this is the easiest way to encode/decode our structs.

Let's define our Struct.

```swift
struct Task: Codable {
	var title: String
	var isDone: Bool
}
```

Before start using PropertyWrappers I'll show you use UserDefaults to save and get our objects.
```swift
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)
	}
}
```

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.

```swift
@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()
        }
    }
}
```


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:

```swift
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)
```

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
```

If you want to check the full implementation please visit this [Gist]([https://gist.github.com/AndresR173/209ebb2c51db3be3c214008d54ea7756](https://gist.github.com/AndresR173/209ebb2c51db3be3c214008d54ea7756))