NSUserDefaults Best Practices

NSUserDefaults

NSUserDefaults is Apple’s way to store user preferences for iOS (it’s for OSX as well but I don’t do OSX Development so I can’t talk at length about that). The data is namespaced into your own persistent domain (usually the name of your app) and cannot be accessed by others apps in iOS as it is sandboxed.

Most people use NSUserDefaults for storing data between sessions. Anything that you would want to persist between sessions such as: a user’s preference for the number of items to display, whether or not somebody has seen a hint about how to use a feature, etc. I’m sure other people utilize NSUserDefaults in more creative and elaborate ways but that is my primary purpose.

I’ve never actually found a good guide on how to use it and best practices with it so I figured I would write one up. This is mostly my own discoveries over the past year of iOS development.

Best Practices

Don’t store sensitive data

NSUserDefaults actually writes all your data to a plist stored in the Documents directory of your application and anybody who has access to the phone can access this data. While your Plist is protected from other apps because each UserDefaults is stored in its own persisted domain, it can be still read from the file system. So any data that you feel might be sensitive you might consider against storing in the UserDefaults or at the very least encrypt it if you absolutely must. I would look into Keychain documentation for this or C solutions. The case I can think of is storing OAuth tokens, either your own app’s tokens or third party tokens (Facebook).

Namespace your keys

So here’s a quirky situation. Third parties libraries can use NSUserDefaults since they are just loaded into the app normally and run under your application’s domain. Most third party libraries know well enough to make sure their keys don’t cause any collisions with anybody else, but you never know. It is easily possible to have a namespace collision with a third party library if both of you aren’t namespacing your keys. Theoretically, you could store all your keys under a different persistent domain but I don’t see any easy to do this and it seems quite cumbersome to include a custom subclass just for this behavior. GVUserDefaults already allows for prepending your keys automatically with a namespace which is pretty awesome.

Thankfully, the third party libraries I use all utilize their own namespaces.

iRateEventCount = 0;
com.facebook.sdk:lastAttributionPing161786420551491” = “2013-12-24 04:28:56 +0000;
AppleITunesStoreItemKinds = {}

It seems the Apple’s convention is to put the name of the app in capital letters, similar to what iRate is doing. ApartmentList currently uses a combination of both ‘ApartmentList’ and ‘.’ is in front of every key. So

ApartmentList.FirstAppLaunchDate” = “2013-12-24 04:30:39 +0000”;

P.S. Warning for those who are going to use GVUserDefaults, Kiwi doesn’t allow for stubbing dynamic methods and GVUserDefaults is all dynamic methods, so you might want to be wary of using that library if you’re doing unit testing using Kiwi. I imagine as soon as Kiwi adds a Before and AfterAll tests configuration it would be easier to set it up since restoring the dictionary on each test sounds like a pain.

Avoid using integerForKey and boolForKey and use objects

So normally you store objects (Strings, Arrays, data) into NSUserDefaults and that allows you to check if the object for that key is nil, you can then store whatever default you want when it is nil. But in case you don’t realize, nil in Objective-c is equivalent to 0.

(lldb) p nil == NO
(bool) $1 = true
(lldb) p nil == 0
(bool) $2 = true

So what happens is that there’s no way to distinguish whether a key has been set or not if you use IntegerForKey and BoolForKey. You cannot tell if the user set a value to 0 or if that is the empty value stored currently.

So my advice for this is actually to only store objects into NSUserDefaults, storing NSNumbers instead of integers and bools Why? Because you don’t have to remember to deal with this edge case and any complications that might come from it. It seems you can take the overhead to remember to worry about if 0 counts as a valid and that defaults values are 0, but it seems easier to just store objects and not worry too much about the performance degradation of this.

I did find one exception to using integerForKey though that if you feel is useful and that can be storing an enum state in your code as long as you don’t mind 0 being the default enum state. This tends to work for the bool state where you want a default of true. BUT, I still thinking saying nil is the empty state is the same as this and communicates that as clearly as an enum without the overhead of keeping track of enums. But this is personal style.

Tips on Migrating your Keys

I find the method – dictionaryRepresentation useful for migrating your keys, once you get a list of keys it’s fairly easy to get the class of the value that the key is storing and set the new value to a namespaced key vs your old key.

If anybody has any other tips feel free to comment below!