I previously explained how to use NSUserDefaults to your advantage, basically being the OSX equivalent to the Windows registry. User Defaults are the way how you can persist small amounts of data in a convenient way. They are basically dictionaries that the system takes care of for you with the added benefit of being cached in memory.
Today we’ll up it one notch. Session 124 of the WWDC 2010 videos brought to my attention the fact that there’s also a mechanism to provide default values for the defaults. Up until now I would have retrieved an object from the defaults, checked if it’s nil and then set the value. Consider the following code snipped from SpeakerClock:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSNumber *countdownNum = [defaults objectForKey:@"Countdown"]; if (countdownNum) { countdown = [countdownNum doubleValue]; } else { countdown = self.currentPreset.startDuration; } |
This works, but it’s not elegant. Wouldn’t it be great if we could pre-register those values so that a simple retrieval always has a result?
It turns out that there is not just one set of defaults that you can write to and read from. There are multiple sets, Apple calls them “domains”. The one we’ve been using so far is called the Application Domain and it’s written to the Library/Preferences folder in you app sandbox. It’s named like your app bundle identifier with a plist extension. It’s really just a property list dictionary on disk.
Other domains available can be either persistant or volatile. Persistant means that they are saved somewhere whereas volatile means that they are only present in memory during the execution of your app. These are all standard domains and they are searched in this order:
- NSArgumentDomain (volatile)
- Application – Identified by the application’s identifier (persistent)
- NSGlobalDomain (persistent)
- Languages (Identified by the language names) volatile
- NSRegistrationDomain volatile
So, we already know the application domain. Languages is set by the system and from these you can get the current UI language. On iPhone all apps are sandboxed, so the global domain is of no use to us. This leaves two potentially useful other domains to explore: the argument domain and the registration domain.
What’s great about this structure is that we don’t have to care about accessing these domains specifically. The applicable value is always searched for us. If it’s not in the application domain, then it’s taken from the registration domain, if present.
Using NSRegistrationDomain
This domain is not written do disk, so you need to fill it with your defaults value every time your app launches. The documentation recommends using the initialize class method which get’s called only once and before a class is accessed the first time. So let’s have those in our application delegate.
+ (void) initialize { // called once before this class is used the first time NSDictionary *defaultsDict = [NSDictionary dictionaryWithObject:@"Touch" forKey:@"Doctor"]; NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; [defaults registerDefaults:defaultsDict]; } - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSString *readValue = [defaults objectForKey:@"Doctor"]; NSLog(@"default for key 'Doctor' is '%@'", readValue); // Override point for customization after app launch [window addSubview:viewController.view]; [window makeKeyAndVisible]; return YES; } |
Put all default values for your defaults into the dictionary you pass to the registerDefaults. This enables the mechanism to always find a value and you no longer have to use tricks like I did above to check if a value is actually set.
It makes sense to put all your default values into a dictionary plist that you add to your app bundle. Then instead of creating the dictionary in code to pass to registerDefaults you can read your prepared dictionary from disk and use this. This way you can also easily modify the defaults without having to change your code.
Arguments
Something that you probably have less use for on iPhone is the arguments domain because iPhone apps will usually not be called with command line arguments. However if you find yourself leveraging your iPhone app knowledge to create a command line tool in Foundation than that’s the most convenient method to retrieve parameter values.
For testing you can supply program arguments in Xcode via the Executables section. It also works for iPhone apps, and such arguments override registration or application domain settings. So it’s great for debugging a setting there.
The exact same code above now outputs ‘Alban’ which was the specified argument. So you override a default setting just to test something without having to add additional code to your project.
For command line tools this replaces the handling of argc and argv in main and gives saves you from having to parse these. As somebody who’s written a couple of tools let me tell you that this is a big convenience.
Conclusion
Actually all of that is explained in the Introduction to User Defaults document but for me somebody had to find out which parts actually apply to the iPhone platform. I tend to read over parts that sound too technical and make me think that they probably don’t work on iPhone.
Knowing about what I wrote here goes a long way to simplifying how your provide default values in your app, how you can quickly override them for testing and how to get arguments from the command line if you chose to write a tool.
Categories: Recipes