“Foil” UserDefaults Property Wrapper
UserDefaults is one of the most misused APIs on Apple platforms. Specifically, most developers do not handle default values correctly. In fact, I have never worked on a single production codebase at a company where this was done accurately. Most libraries get it wrong, too.
[…]
There are a few libraries that currently provide a property wrapper for
UserDefaults
. However, the ones that I know about each have a combination of the following issues: (1) default values are not registered, (2) optionals are not handled nicely, (3) the library is extremely complicated for such a simple task.
I like his approach. Mine differs in that:
- Instead of casting the result of
UserDefaults.object(forKey:)
, it uses the type-specific methods likebool(forKey:)
andinteger(forKey:)
, so that if the value is of an unexpected type (which can easily happen when usingdefaults write
) it won’t crash or returnfalse
when the string wasYES
. The initializer uses the magic
wrappedValue
parameter, so that the default value can be provided in the property declaration itself. This also allows the property’s type to be inferred.The property wrapper caches values read from
UserDefaults
. I’ve found that, even with the overhead of making the cache threadsafe, this can really speed things up.In order to make sure the cache remains valid, it observes when each default is modified. Clients can do this, too, e.g. to see when another part of the app has updated a preference.
Previously:
- UserDefaults Access via Property Wrappers
- Swift Property Wrappers
- Type-Safe User Defaults in Swift
- Use and Misuse of NSUserDefaults
- Debugging NSUserDefaults
4 Comments RSS · Twitter
My Cocoa projects have always had a resource named DefaultDefaults.plist which is loaded into the registration domain. Defining initial/default values in code has always seemed like a code smell to me.
@Mark I like doing it in code this way because then the compiler can check that each key has a default value. With a separate plist file, there are two separate places to update and keep in sync, and there’s the chance of mistyping one of the keys so they don’t match. Also, I often want a different default value depending on the version of macOS or some other factor, and that’s easier to do in code.
I’m also a fan of establishing initial values in code instead of registering them via a separate plist file, for all the reasons Michael stated. Though I rarely found a need to cache any values except in very frequently called code, eg: drawing code that consulted user defaults.
@Martin Yes, I am primarily thinking of drawing code. In Big Sur, navigating the message list in Mail feels a lot slower to me, and when I sampled it this seems to be partially because it’s repeatedly reading multiple defaults (e.g. the blocked sender setting) for each message.