Even though the UserDefaults
class offers an easy-to-use API, I always create an extension for the UserDefaults
class. Why is that? What are the benefits of an extension for UserDefaults
? That is the focus of this post.
Convenience Properties and Methods
In another post, I explained how to check if a key-value pair exists in the user's defaults database. This is as simple as invoking the object(forKey:)
method on the shared defaults object. If that method returns nil
, then the key-value pair doesn't exist.
import Foundation
if UserDefaults.standard.object(forKey: "myKey") == nil {
print("key/value pair doesn't exist")
} else {
print("key/value pair exists")
}
In that post, we created an extension for UserDefaults
and implemented a convenience method for checking whether a key-value pair existed or not. This is what that extension looked like.
import Foundation
extension UserDefaults {
func valueExists(forKey key: String) -> Bool {
return object(forKey: key) != nil
}
}
if UserDefaults.standard.valueExists(forKey: "myKey") {
print("key/value pair exists")
} else {
print("key/value pair doesn't exist")
}
The result is a more intuitive and more elegant API that is easy to read and use.
Avoiding Typos
If you use string literals, then you run the risk of introducing bugs simply by making a typo. I don't like string literals and it is easy to avoid typos by eliminating string literals from your project. Let me show you the technique I use in every project I work on.
Create an extension for UserDefaults
and define a private enum Keys
. Define a static, constant property, myKey
, and assign a string literal to it, myKey
. This is the only place where you define the key. We privately define the enum Keys
to make sure it isn't accessible from elsewhere in the project.
import Foundation
extension UserDefaults {
private enum Keys {
static let myKey = "myKey"
}
}
Let's assume the value of the key myKey
is a boolean. We can easily create a convenience property for safely modifying the value of myKey
. Because we almost always interact with the shared defaults object, I define the property as a class property with a setter and a getter.
import Foundation
extension UserDefaults {
private enum Keys {
static let myKey = "myKey"
}
class var myKey: Bool {
get {
}
set {
}
}
}
The implementation of the getter is easy to understand. We ask the shared defaults object for the value for key myKey
. We no longer use a string literal for the key. We use the Keys
enum instead.
import Foundation
extension UserDefaults {
private enum Keys {
static let myKey = "myKey"
}
class var myKey: Bool {
get {
return UserDefaults.standard.bool(forKey: Keys.myKey)
}
set {
}
}
}
Implementing the setter is just as simple. We access the shared defaults object and invoke the set(_:forKey:)
method, passing in newValue
. The newValue
constant is automatically available in the setter.
import Foundation
extension UserDefaults {
private enum Keys {
static let myKey = "myKey"
}
class var myKey: Bool {
get {
return UserDefaults.standard.bool(forKey: Keys.myKey)
}
set {
UserDefaults.standard.set(newValue, forKey: Keys.myKey)
}
}
}
What Are the Benefits?
The benefits of this approach are clear. First, we have eliminated the risk of typos. As long as the string literals of the Keys
enum are correct, typos are a thing of the past. Second, we have created a beautiful, elegant, and most important, typesafe API by implementing a class method using an extension for UserDefaults
. Take a look at the API in action.
// Getting
print(UserDefaults.myKey)
// Setting
UserDefaults.myKey = true
It takes a few minutes to write the extension, but the benefits far outweigh the time it takes to implement the convenience property. Do you agree?
We only scratched the surface in this post. You can add convenience properties for custom objects, including enums, and you can create convenience methods for more complex operations. As I mentioned earlier, I use this technique in every project I work on because it is so convenient and results in a readable, elegant API.