Monday, February 22, 2016 [Tweets] [Favorites]

Use and Misuse of NSUserDefaults

David Smith (tweet):

If you find yourself needing to do anything else to read a preference, you should take a step back and reconsider: caching values from NSUserDefaults is usually unnecessary, since it’s extremely fast to read from. Calling -synchronize before reading a value is always unnecessary. Responding when the value changes is almost always unnecessary, since the nature of “settings” is that they control what a program does when it does it, rather than actually causing it to do something. Having an alternate code path for “no value set” is also generally unnecessary, as you can provide a default value instead (see Providing Default Values below).

[…]

You can call -registerDefaults: as many times as you like, and it will combine the dictionaries that you pass it, which means you can keep registration of settings near the code that cares about them.

I used to use -registerDefaults: more, but now I mostly use a category method like:

- (id)mjtObjectForKey:(NSString *)key defaultValue:(id)defaultValue;

that returns defaultValue if NSUserDefaults returns nil. This is more convenient (one line of code) and makes it impossible to look up a key that has not been registered.

In the sandboxed world of modern OSX and all iOS versions, NSUserDefaults is initially limited to operating in your app’s sandbox; if you use -initWithSuiteName: you’ll just get a new store of user defaults that’s still not shared.

[…]

NSUserDefaults does not have any form of transaction system, so there’s no way to guarantee that multiple changes will only be seen all at once. Another program could see the first change before the second finishes.

[…]

Neither NSUserDefaultsDidChangeNotification nor KVO notify of changes made by other programs

-registerDefaults: operates on every NSUserDefaults instance, not just the one you call it on

Update (2016-02-22): One of the issues I’ve found is that, starting with Mac OS X 10.10, NSUserDefaults ignores any attempt to set an object that is a dictionary or array bridged from Python.

6 Comments

NSUserDefaults has become less relevant, on iOS it is little more than a key value store, the global, application and volatile domains mean next to nothing. If you're using sqlite already might as well setup your own database and avoid issues like this http://stackoverflow.com/questions/20269116/nsuserdefaults-loosing-its-keys-values-when-phone-is-rebooted-but-not-unlocked

I disagree with you both, in that I recommend explicitly writing out the defaultValue in the user defaults: this makes them more discoverable: http://wanderingcoder.net/2012/12/31/discoverable-defaults/ , and the "subtle flaw" he mentions is to my mind the better behavior, as the previous default value acts as a sort of "common law feature" and therefore does not change for existing users even if you put a new one, with the new default only applying for new installs.

@Pierre What do you do if you want to change the default value? Make a new key? Would do you do with the stored value for the obsolete key? Is there a distinction between preferences that are exposed in the user interface and ones that are just internal settings that you may need to tweak in the future? For the latter, I would definitely not want to preserve the previous default value.

Writing out default values is a bad idea, it negates the global domain in the search.

@Michael : Good point, I only considered “explicitly settable” prefs (typically those that appear in the preferences dialog and/or the Settings app) in my analysis; and even just in that case, it could depend on the situation: for instance you might want to decrease the default for refresh from once an hour to once a day if you’re having capacity issues, and have it apply to most existing installs (those where the user hasn't changed the default). My point, if I have one, is that in most cases not changing the pref after having seen its effect once is a choice as well from the user, and it should be respected to some extent. Your mileage may vary.

@Pierre: in your example, how would you determine whether the user had changed the default? Maybe she changed it to something else, and then back to the current value (which happens to be your stock default) because that's what she wanted. (Then your new change would usurp her choice.)

This scenario is the whole reason for “registered” (non-committed) defaults.

Stay up-to-date by subscribing to the Comments RSS Feed for this post.

Leave a Comment