Design pattern/best practice for UserDefault values?

I am new to Swift and am developing an application that has a few dozen values that it needs to store in UserDefaults - login credentials, tuning parameters, etc - that often need to be accessed during the app's execution. With how extensible the language seems to be I am wondering if there is a clean way to dynamically manage the large number of variables that this would entail. This is the closest I could come up with.


First, set up an enum for all of the keys that will be used:

extension UserDefaults {
    enum Keys : String{
        case userName, password, serverIP, syncInterval, ......
    }

Next, set up the defaults:

     let defaults:[String:Any] =
            [UserDefaults.Keys.userName.rawValue : "",
             UserDefaults.Keys.password.rawValue : "",
             UserDefaults.Keys.serverIP.rawValue : "0.0.0.0",
             UserDefaults.Keys.syncInterval.rawValue : 60,
             ......]

     UserDefaults.standard.register(defaults:defaults)

Next, define a computed property for each value:

    class var serverIP: String {
        get {
            return UserDefaults.standard.string(forKey: UserDefaults.Keys.serverIP.rawValue)!
        }
        set {
            UserDefaults.standard.set(newValue, forKey: UserDefaults.Keys.serverIP.rawValue)
        }
    }
    class var syncInterval: Int {
        get {
            return UserDefaults.standard.integer(forKey: UserDefaults.Keys.syncInterval.rawValue)
        }
        set {
            UserDefaults.standard.set(newValue, forKey: UserDefaults.Keys.syncInterval.rawValue)
        }
    }
    ......


And then just use UserDefaults.serverIP in the code whenever I need to make a web request. I like how this reads but I am concerned about whether it is too inefficient to load from UserDefaults every time?

Also, it annoys me how independent and manual these three segments of code are; is there a way to dynamically build a list of enums and/or computed properties based on a dictionary of default values? Or some other approach to build a way of accessing these properties dynamically?


Maybe I'm just overthinking things but this seems like a not uncommon use case, so I'm hoping there are some elegant solutions out there. Thanks!

First up, you should definitely store credentials, like your password, in the keychain rather than user defaults. That will give it good security by default, and allow you to override that default security as is necessary for your app.

Second, on the performance front, I wouldn’t worry too much about performance here;

UserDefaults
does a lot of its own caching so you’re not hitting the disk every time. Accessing a user default in a tight loop that’s run thousands of times a second would be a problem, but accessing a user default for every network request is fine (the cost of the user default access will be swamped by the networking cost).

Finally, with regards code structure, you wrote:

Also, it annoys me how independent and manual these three segments of code are; is there a way to dynamically build a list of enums and/or computed properties based on a dictionary of default values?

There are lots of ways to do this, ranging from the simple-but-wordy approach you’ve shown to compact-but-complex approaches base on runtime introspection. I tend to err on the side of the simple here, because I like to be able to understand my code when I come back to it 5 years from now (-:

With regards your current approach, my biggest concern here is that your

enum
doesn’t buy you much. It would be nice if you had a central place to tie together the key, type and default value of a specific user default. It seems like a
struct
would be the best option for that:
private struct UserDefaultInfo<Value> {
    var key: String
    var defaultValue: Value
}

You can then add an extension to get and set:

private extension UserDefaultInfo {

    func get() -> Value {
        guard let valueUntyped = UserDefaults.standard.object(forKey: self.key) else {
            return self.defaultValue
        }
        guard let value = valueUntyped as? Value else {
            return self.defaultValue
        }
        return value
    }

    func set(_ value: Value) {
        UserDefaults.standard.set(value, forKey: self.key)
    }
}

And finally, add a wrapper to present the API you want:

enum MyUserDefaults {

    static var serverIP: String {
        get { return serverIPInfo.get() }
        set { serverIPInfo.set(newValue) }
    }

    private static var serverIPInfo = UserDefaultInfo(key: "serverIP", defaultValue: "")
}

I’m sure that this isn’t the last word in user defaults management, and I’d be interested in what other folks have to say about this too.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Hi, thanks for your response.

First, I had a few questions about general Swift coding, based on your reponses:

1. Is there a difference between defining everything in one block and splitting up the definition and extension of the struct as you did? Is it just a stylistic choice?

2. Why did you define

MyUserDefaults
as an enum instead of a class? What does an enum without
case
s mean?


Anyway, glad to hear that UserDefaults is pretty safe efficiency-wise. I will be accessing many values throughout the course of the sync so it's way more than just one per network call, but it's certainly not thousands per second.


I do like your solution more than mine, but I'm still wondering if there's a way to make it cleaner - defining the get and set functions for all of the properties feels pretty redundant. But I do realize that trying to genericize it further would mean dealing with the

Any
type and casting everywhere.


Thanks again!

1. Is there a difference between defining everything in one block and splitting up the definition and extension of the struct as you did?

There can be subtle differences but in this case it was just a stylistic choice.

2. Why did you define MyUserDefaults as an enum instead of a class?

This is a common Swift idiom for namespacing. You can learn more by reading this article.

I do like your solution more than mine, but I'm still wondering if there's a way to make it cleaner …

Probably (-: For example, you could start playing silly runtime tricks using

@dynamic
. I think this is about balance: you have to balance the safety and easy of use at the call site (meaning want to avoid
Any
) against the ease of maintenance (hence no
@dynamic
) against the amount of boilerplate. The above is where my own personal balance came to rest, but I’m sure it’ll be different for others.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
Design pattern/best practice for UserDefault values?
 
 
Q