Create the Perfect UserDefaults Wrapper Using Property Wrapper

No more boilerplate code and increase reusability

Lee Kah Seng
Oct 19 · 5 min read
No more boilerplate code

Imagine you have an app and you want to implement auto-login functionality. Thus, you create a UserDefaults wrapper to encapsulate the UserDefaults read and write logic.

You will use the UserDefaults wrapper to keep track of the auto-login on/ off status, as well as the user's username. This is what your UserDefaults wrapper usually looks like:

With property wrapper, introduced in Swift 5.1, you can simplify your UserDefaults wrapper into this:

Pretty awesome right? Want to know more? Read on…

What Is Property Wrapper?

Before we get into the details, let’s have a quick introduction to what a property wrapper is.

Basically, a property wrapper is a generic data structure that can intercept the property’s read/write access, thus enabling custom behavior being added during the property’s read/write operation.

To define a property wrapper, you can use the keyword @propertyWrapper. Let's say you want to have a string type property that every time it is being read or write, a console log will be printed.

You can create a property wrapper, named Printable, as shown below:

As you can see from the code above, property wrapper is just like any other struct in Swift. However, a wrappedValue is compulsory when defining a property wrapper.

The wrappedValue get and set block is where you can intercept and perform the operation you desire. In this example, a print statement is added to print out the value being get or set.

Here’s how you can use the Printable property wrapper:

Note that we use the @ symbol to declare the name variable that’s wrapped by property wrapper. If you try out the above code in your Xcode playground, you will see the console output as shown below:

Set value: Adidas
Get value: Adidas

The UserDefaults Wrapper

After understanding how property wrapper works, we are now ready to start implementing our UserDefaults wrapper. To recap, our property wrapper needs to keep track of the auto-login on/off status, as well as the user's username.

By using the concept we discussed above, you can easily convert the Printable property wrapper into a property wrapper that will write to or read from UserDefaults during a property read/write operation.

Here, we named our property wrapper Storage. It has two properties which are key and defaultValue.

key will be the key to use when reading and writing to UserDefaults, and defaultValue is the value to return when there is no value in UserDefaults.

With the Storage property wrapper ready, we can start implementing the UserDefaults wrapper. It is pretty straightforward, we just need to create a username variable that is wrapped by the Storage property wrapper.

Do note how you can initialize the Storage property wrapper with key and defaultValue.

With all that, the UserDefaults wrapper is finally ready to use. Let's see it in action:

At this point, let’s try to add the enableAutoLogin variable into our UserDefaults wrapper.

However, you will notice that the following two errors occurred:

Cannot convert value of type ‘Bool’ to expected argument type ‘String’

This is because our property wrapper currently only supports the String data type. To fix both errors, we will have to make our property wrapper generic.

Making the Property Wrapper Generic

To make the property wrapper generic, we have to change the property wrapper’s wrappedValue data type from String to a generic type T.

Furthermore, we will have to update the wrappedValue get block to use a generic way to read from UserDefaults. Here's the updated property wrapper:

With a generic property wrapper, our UserDefaults wrapper can now store a boolean value without any problem.

Storing Custom Objects

At this point, our UserDefaults wrapper is able to store any basic data type, such as String, Bool, Int, Float, Array, etc.

But what if we need to store a custom object? Currently, we will encounter an error if we try to store a custom object. In this section, let's make our UserDefaults wrapper more awesome by enabling it to support custom objects.

The concept here is simple, we will store the custom object as data in UserDefaults. To achieve that, we must update the Storage property wrapper generic type T to conform to the Codable protocol.

After that, in the wrappedValue set block, we will use JSONEncoder to convert the custom object to data and write it to UserDefaults.

Meanwhile, in the wrappedValue get block, we will use JSONDecoder to convert the data we retrieved from UserDefaults back to the desired data type.

Here’s the updated Storage property wrapper:

To see how you can use the updated Storage property wrapper, let's take a look at the following example.

Imagine you need to store the user’s information returned by the server-side after a user’s successful login.

First, you will need a struct to hold the user information returned by the server. The struct must conform to the Codable protocol so that it can be converted to data and stored into UserDefaults.

The next step is to declare a User object in the UserDefaults wrapper:

That’s it! The UserDefaults wrapper is now able to store custom objects.

Storing Encrypted String

We have gone a long way by making our UserDefaults wrapper generic and able to store basically anything that we desire.

But wait, what if you need to store the user password or any sensitive data using the UserDefaults wrapper?

Currently, all the strings that are being stored in our UserDefaults wrapper are plain text and we all know that storing passwords as plain text is an extremely bad practice!

To go about this, we can use the concept that we have just discussed, create another property wrapper that will encrypt its value before setting it into UserDefaults. We will call this property wrapper EncryptedStringStorage.

For demo purposes, the encryption we do here is just a simple operation of reversing the entire string. The way to use the EncryptedStringStorage property wrapper is fairly straightforward:

Wrapping Up

By using the property wrapper introduced in Swift 5.1, we have reduced a lot of boilerplate code in our UserDefaults wrapper.

Furthermore, both the Storage property wrapper and the EncryptedStringStorage property wrapper can also be re-used in other projects.

Next time when you need to create a UserDefaults wrapper, give the property wrapper method a shot, I am sure you will like it.

You can find the full source code here.

I hope you find this article useful. Feel free to leave a comment if you have any questions or thoughts regarding the UserDefaults wrapper.

Thanks for reading and happy coding!

Better Programming

Advice for programmers.

Lee Kah Seng

Written by | iOS developer since 2011 | Swift Lover | Music Addict | Anime Fan | Part-time Backpacker

Better Programming

Advice for programmers.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade