2.6 User Defaults

User defaults is another term for user application preferences. Mac OS X has a well-designed user defaults system that is accessed in Cocoa through the Foundation class NSUserDefaults. Working with NSUserDefaults is similar to working with an NSDictionary. Default values are stored in the database by keys that the application developer defines in the application. The defaults database is actually a collection of property list files; every application has its own property list file where defaults are stored. You can view these files in ~/Library/Preferences.

Defaults are organized into domains, which are groupings of default values that have varying degrees of visibility to applications. A domain is either persistent or volatile. Defaults in a persistent domain are stored in the defaults database, while defaults in a volatile domain are applicable only during the lifetime of the NSUserDefaults object that contains those values. NSUserDefaults has five standard domains:

NSArgumentDomain

Set values for defaults in the argument domain by passing key-value pairs to the application as arguments on the command line, (e.g., % MyApp -KeyName Value). The argument domain is volatile, so arguments affect the application only during the application session for which they were specified.

Application

Application-specific defaults are stored here and kept persistently in the user's defaults database.

NSGlobalDomain

Defaults stored in the global domain are applicable to all applications run by the user. This persistent domain is stored in the defaults database.

Languages

The languages domain stores defaults that pertain to language choice and localization.

NSRegistrationDomain

The registration domain is the lowest-level domain containing application-provided defaults (or "factory settings") used when a default value is otherwise unspecified in a higher domain.

When a default is requested, the domains are searched for the value in order, starting with NSArgumentDomain and ending with NSRegistrationDomain. The search ends at the first discovery of a default value. Thus, if many domains have values for the same default, NSUserDefaults returns the default that occurred in the higher-level domain. You can exploit the search order as a debugging aid by overriding any default by specifying a value in the NSArgumentDomain.

User defaults are capable of storing only what property lists can store, namely NSData, NSNumber, NSString, NSDate, NSArray, and NSDictionary (although convenience methods are also provided to get and set scalar values). Using these data types, you can store information, such as dates, numbers, and text, as well as any object that is archiveable. Example 2-33 shows how to interact with the user defaults system.

Example 2-33. Interacting with the user defaults system using NSUserDefaults
// Create an instance of NSUserDefaults
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];

// Store and retrieve a string
[prefs setObject:@"Mike Beam" forKey:@"Author"];
NSString *author = [prefs stringForKey:@"Author"];

// Store and retrieve a number
[prefs setFloat:1373.50 forKey:@"NASDAQ"];
[prefs setInt:2002 forKey:@"Year"];
float level = [prefs floatForKey:@"NASDAQ"];
int year = [prefs intForKey:@"Year"];

// Store and retrieve dates
[prefs setObject:[NSDate date] forKey:@"Last Opened"];
NSDate *lastOpenDate = [prefs objectForKey:@"Last Opened"];

// Store collections
[prefs setObject:dictionary forKey:@"A Dictionary"];
[prefs setObject:array forKey:@"An Array"];

// Retrieve collections
NSArray *array = [prefs arrayForKey:@"An Array"];
NSDictionary *dict = [prefs dictionaryForKey:@"A Dictionary"];

// Use the following if you want mutable objects...
NSMutableArray *mArray = [NSMutableArray 
    arrayWithArray: [prefs arrayForKey:@"An Array"]];
NSMutableDictionary *mDict = [NSMutableDictionary
    dictionaryWithDictionary: [prefs dictionaryForKey:@"A Dictionary"]];

All applications should establish factory default settings in the registration domain. This is done with the registerDefaults method. Establishing defaults in the registration domain often takes place in an overridden initialize class method of one of the first classes loaded in your application. This method works well because it is used to initialize classes when they are first loaded by the runtime system, and it is thus one of the earliest entry points in code execution. This example shows how it might be done for a small number of defaults:

+ (void)initialize
{
    NSUserDefaults *prefs;
    NSMutableDictionary *defs;

    prefs = [NSUserDefaults standardUserDefaults];
    [defs setObject:@"May" ForKey:@"Month"];
    [defs setInteger:2002 ForKey:"Year"];
    [prefs registerDefaults:defs];
}

If you need to register a large number of defaults, hardcoding them this way might prove cumbersome. For these situations, it may be more convenient to store factory settings in a property list included with the application, which is then read into a dictionary and registered with user defaults:

+ (void)initialize
{
    NSString *prefsFile;
    NSUserDefaults *prefs;
    NSDictionary *defs;

    // The factory defaults file is a resource in the application
    // bundle. The path is retrieved using NSBundle.
    prefsFile = [[NSBundle mainBundle] 
            pathForResource:@"FactoryDefaults" 
            ofType:@"plist"];

    defs = [NSDictionary dictionaryithContentsOfFile:prefsFile];
    prefs = [NSUserDefaults standardUserDefaults];
    [prefs registerDefaults:defs];
}

One commonly stored preference is an NSColor. There are, however, no provisions for storing a color directly in the defaults database. One way to store information about colors in the defaults database is to store the color space name and a dictionary of the color component values.. All of these data types are supported by NSUserDefaults. A better solution is to archive the NSColor object into an NSData instance and store it in the preferences, as shown in Example 2-34.

Example 2-34. Storing an NSColor to user defaults
// Assume NSColor object color exists
// Store the color
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
NSData *colorData;
colorData = [NSArchiver archivedDataWithRootObject:color];
[prefs setObject:colorData forKey:@"Text Color"];

// Retrieve the color
colorData = [prefs dataForKey:@"Text Color"];
id color = [NSUnarchiver unarchiveObjectWithData:colorData];

This technique is not limited to only NSColor. It can work with any class of object that conforms to the NSCoding protocol, the prerequisite for compatibility with Foundation's archiving system.



    Part II: API Quick Reference