Recommended way to provide default value for NSDefault?

Our app make extensive use of NSDefault. We have around 50 keys.

Currently, we prefer to have default value from NSDefault, when the value for a key is not set before.

We notice there are 2 ways to do achieve so.

register

UserDefaults.standard.register(defaults: ["TRASH_SORT_OPTION" : true])

optionalBool, optionalInt, ...

extension UserDefaults {

    public func optionalInt(forKey defaultName: String) -> Int? {
        let defaults = self
        if let value = defaults.value(forKey: defaultName) {
            return value as? Int
        }
        return nil
    }

    public func optionalBool(forKey defaultName: String) -> Bool? {
        let defaults = self
        if let value = defaults.value(forKey: defaultName) {
            return value as? Bool
        }
        return nil
    }
}


let x = defaults.optionalBool(forKey: "TRASH_SORT_OPTION") ?? true

I was wondering, which is a preferable way? I have a feeling that if the number of keys is large, using "register" method can easily make mistake.

Have a look at UserDefault property wrapper?

1 Like

While a property wrapper like that is convenient, it poses an issue where you could end up with different default values for the same key which might be confusing. This would happen if you have the same key used in two places and you specify a different default value for each. You would end up with the default value for the first property you accessed. This might be what you want, but it could also be unexpected if you're not aware of this.

I personally like to add an extension to UserDefault where I access my properties from and where I'd provide default values if it makes sense:

extension UserDefaults {
  var someString: String {
    get { return string(forKey: "dw.UserDefaults.someString") ?? "Default Value" }
    set { set(newValue, forKey: "dw.UserDefaults.someString") }
  }
}
1 Like

I maintain Defaults, which is a package that makes UserDefaults type-safe. You register your keys with their type and default value once and then use them in a type-safe manner everywhere. This package does not have the problem with multiple default values like the mentioned property wrapper.

1 Like

UserDefaults are global, so there should only be one of each..I use it like this:

enum Settings {
    @UserDefault(key: "foo",  defaultValue: 9.50)  static var foo
    @UserDefault(key: "bar",  defaultValue: 18.00) static var bar
}

So there is no way to duplicate

@UserDefault allow us to rid of boilerplate.

Like @sindresorhus said, proper UserDefaults usage registers default values at app launch before being used. It shouldn't be part of a property wrapper.

[former NSUserDefaults maintainer hat on]

Please use register :)

1 Like

I'm curious to hear more if you'd be willing to share. Is the benefit of using register just the fact that you're defining all of your default values in a centralized place, or is there more to it than that?

1 Like

Pretty much just that. It avoids the possibility of different parts of the codebase disagreeing on what the value is, and it means changing the default value doesn't require changing multiple places.

The real antipattern is

if not set {
    set (not register) default value
}

but everyone here appears to know to avoid that one.

1 Like

But, isn't that it seems highly inefficient in term of runtime? Consider that you need to do the same "register" over and over again, every-time during app start?

Registering is cheap. No IO, no IPC*.

  • a few older OS versions did IPC for reasons but that got fixed.
1 Like

Not really.

The Registration Domain is never written out to disk and its added at the end of the defaults search order so its only read if nothing else satisfies the search for a key value.

The idea is that you use this domain to provide default values that your app really needs to not crash at runtime. For optional things the UserDefaults methods return values that you can use if there isn't a value listed for the key.

The way I normally use registration is to have a plist of my default settings in my app bundle that I read in at launch. That way updating the basic defaults doesn't involve any code.

It seems to me that the question still stands due to Swift requiring the caller to handle the optional value. Back when NSUserDefaults was created, there weren't any nullability annotations so any of the "value for key" methods could return nil but the compiler didn't require us to handle them. (And, of course, that's where the "register default values so your app doesn't crash" recommendation comes from.)

But now, from Swift, we can indeed use the original default value registration mechanism, but the types on the "value for key" methods still return optional types (ignoring the obvious primitive exceptions like Double and Bool).

If you register a default, you could force unwrap it, but then you'd be doing it everywhere you request it. It naturally leads to the idea of putting custom properties in an extension on UserDefaults. And then it naturally follows to just use nil coalescing for the default value instead of force unwrapping to return the non-optional type.

Once that road is taken, then why register the default values at all? The defaults are defined in one place in the extension, as the registration mechanism was supposed to help achieve. And we're leveraging Swift's type system, too.

This makes me wonder if registering defaults is something we should still be recommending in Swift.

2 Likes

This definitely seems like an area where the Swift overlay could be made nicer :)

Heh, probably. :) Though I'm having trouble imaging what that would be.

I've rarely (or never) seen registration used in iOS apps. When I used to write Mac apps I always used it. On iOS, never. The system domain has no use on iOS. That only leaves user domain and registration domain. In addition I bet that most iOS devs have no idea that registration exists.

I would guess that all code I've seen on iOS uses UserDefaults like a simple dictionary. It's simple enough to check for nil and handle the case where the value doesn't exist. We do it all day long in the rest of our apps.

I think I have in one app a set of wrapper functions that get a value but pass in the default value. So either the existing value or the default is returned. Simple enough.

As many other posters on this thread, I'd like to know want the benefits of registering might be. The benefit of not registering defaults is that I can see if a value has ever been changed.

1 Like

The benefit of not registering defaults is that I can see if a value has ever been changed.

I'm curious why you'd want to know this in practice.

  1. Support: Users inquiring about a supported feature should be pointed to the place where they can activate it if they have never tried it, users who did try require a more sophisticated response.

  2. Update-related changes: As an example, I used dark/light UI schemes in an app, with a selector. As iOS 13 came along, I switched to a three-way selector (dark/light/automatic, the latter depending on the new system setting and switching with day/night schedules). New installers get automatic, previous users get what they selected actively, or automatic if they didn't bother.