Create the Perfect UserDefaults Wrapper Using Property Wrapper
No more boilerplate code and increase reusability
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.
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
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 will be the key to use when reading and writing to
defaultValue is the value to return when there is no value in
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
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
However, you will notice that the following two errors occurred:
Cannot convert value of type ‘Bool’ to expected argument type ‘String’Property type ‘Bool’ does not match that of the ‘wrappedValue’ property of its wrapper type ‘Storage’
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
Furthermore, we will have to update the
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
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
After that, in the
set block, we will use
JSONEncoder to convert the custom object to data and write it to
Meanwhile, in the
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
The next step is to declare a
User object in the
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
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
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:
By using the property wrapper introduced in Swift 5.1, we have reduced a lot of boilerplate code in our
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
Thanks for reading and happy coding!