Accessing Preference Values

You use the NSUserDefaults class to gain access to your app’s preferences. Each app is provided with a single instance of this class, accessible from the standardUserDefaults class method. You use the shared user defaults object to:

Mac apps that use Cocoa bindings can use an NSUserDefaultsController object to set and get preferences automatically. You typically add such an object to the same nib file you use for displaying user-facing preferences. You bind your user interface controls to items in the user defaults controller, which handles the process of getting and setting values in the user defaults database.

Preference values must be one of the standard property list object types: NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. The NSUserDefaults class also provides built-in manipulations for storing NSURL objects as preference values. For more information about property lists and their contents, see Property List Programming Guide.

Registering Your App’s Default Preferences

At launch time, an app should register default values for any preferences that it expects to be present and valid. When you request the value of a preference that has never been set, the methods of the NSUserDefaults class return default values that are appropriate for the data type. For numerical scalar values, this typically means returning 0, but for strings and other objects it means returning nil. If these standard default values are not appropriate for your app, you can register your own default values using the registerDefaults: method. This method places your custom default values in the NSRegistrationDomain domain, which causes them to be returned when a preference is not explicitly set.

When calling the registerDefaults: method, you must provide a dictionary of all the default values you need to register. Listing 2-1 shows an example where an iOS app registers its default values early in the launch cycle. You can register default values at any time, of course, but should always register them before attempting to retrieve any preference values.

Listing 2-1  Registering default preference values

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
   // Register the preference defaults early.
    NSDictionary *appDefaults = [NSDictionary
        dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:@"CacheDataAgressively"];
    [[NSUserDefaults standardUserDefaults] registerDefaults:appDefaults];
 
   // Other initialization...
}

When registering default values for scalar types, use an NSNumber object to specify the value for the number. If you want to register a preference whose value is a URL, use the archivedDataWithRootObject: method of NSKeyedArchiver to encode the URL in an NSData object first. Although you can use a similar technique for other types of objects, you should avoid doing so when a simpler option is available.

Getting and Setting Preference Values

You get and set preference values using the methods of the NSUserDefaults class. This class has methods for getting and setting preferences with scalar values of type Boolean, integer, float, and double. It also has methods for getting and setting preferences whose value is an object of type NSData, NSDate, NSString, NSNumber, NSArray, NSDictionary, and NSURL. There are two situations where you might get preference values and one where you might set them:

The following code shows how you might get a preference value in your code. In this example, the code retrieves the value of the CacheDataAggressively key, which is custom key that the app might use to determine its caching strategy. Code like this can be used anywhere to handle custom configuration of your app. If you wanted to display this particular preference value to the user, you would use similar code to configure the controls of your preferences interface.

if ([[NSUserDefaults standardUserDefaults] boolForKey:@"CacheDataAggressively"]) {
   // Delete the backup file.
}

To set a preference value programmatically, you call the corresponding setter methods of NSUserDefaults. When setting object values, you must use the setObject:forKey: method. When calling this method, you must make sure that the object is one of the standard property list types. The following example sets some preferences based on the state of the app’s preferences interface.

NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
if ([cacheAgressivelyButton state] == NSOnState) {
   // The user wants to cache files aggressively.
   [defaults setBool:YES forKey:@"CacheDataAggressively"];
   [defaults setObject:[NSDate dateWithTimeIntervalSinceNow:(3600 * 24 * 7)]
             forKey:@"CacheExpirationDate"]; // Set a 1-week expiration
} else {
    // The user wants to use lazy caching.
   [defaults setBool:NO forKey:@"CacheDataAggressively"];
   [defaults removeObjectForKey:@"CacheExpirationDate"];
}

You do not have to display a preferences interface to manage all values. Your app can use preferences to cache interesting information. For example, NSWindow objects store their current location in the user defaults system. This data allows them to return to the same location the next time the user starts the app.

Synchronizing and Detecting Preference Changes

Because the NSUserDefaults class caches values, it is sometimes necessary to synchronize the cached values with the current contents of the user defaults database. Your app is not always the only entity modifying the user defaults database. In iOS, the Settings app can modify the values of preferences for apps that have a Settings bundle. In OS X, the system and other apps might modify preferences values in response to user actions. For example, if the user changes preferred languages, the system writes the new values to the user defaults database. In OS X v10.5 and later, the shared NSUserDefaults object synchronizes its caches automatically at periodic intervals. However, apps can call the synchronize method manually to force an update of the cached values.

To detect when changes to a preference value occur, apps can also register for the notification NSUserDefaultsDidChangeNotification. The shared NSUserDefaults object sends this notification to your app whenever it detects a change to a preference located in one of the persistent domains. You can use this notification to respond to changes that might impact your user interface. For example, you could use it to detect changes to the user’s preferred language and update your app content appropriately.

Managing Preferences Using Cocoa Bindings

Mac apps can use Cocoa bindings to set preference values directly from their user interfaces. Modifying preferences using bindings involves adding an NSUserDefaultsController object to the appropriate nib files and binding the values of your controls to the preference values in the user defaults database. When your app shows the interface, the user defaults controller automatically loads values from the user defaults database and uses them to set the value of controls. Similarly, when the user changes the value in a control, the user defaults controller updates the value in the user defaults database.

For more information on how to use the NSUserDefaultsController class to bind preference values to your user interface, see “User Defaults and Bindings” in Cocoa Bindings Programming Topics.

Managing Preferences Using Core Foundation

The Core Foundation framework provides its own set of interfaces for accessing preferences stored in the user defaults database. Like the NSUserDefaults class, you can use Core Foundation functions to get and set preference values and synchronize the user defaults database. Unlike NSUserDefaults, you can use the Core Foundation functions to write preferences for different apps and on different computers. Note that modifying some preferences domains (those not belonging to the current app and user) requires root privileges (or admin privileges prior to OS X v10.6); for information on how to gain suitable privileges, see Authorization Services Programming Guide. Writing outside the app domain is not possible for apps installed in a sandbox.

For information about the Core Foundation functions for getting and setting preferences, see Preferences Utilities Reference.

Setting a Preference Value Using Core Foundation

Preferences are stored as key-value pairs. The key must be a CFString object, but the value can be any Core Foundation property list value (see Property List Programming Topics for Core Foundation), including the container types. For example, you might have a key called defaultWindowWidth that defines the width in pixels of any new windows that your app creates. Its value would most likely be of type CFNumber. You might also decide to combine window width and height into a single preference called defaultWindowSize and make its value be a CFArray object containing two CFNumber objects.

The code in Listing 2-2 demonstrates how to create a simple preference for the app MyTextEditor. The example sets the default text color for the app to blue.

Listing 2-2  Writing a simple default

CFStringRef textColorKey = CFSTR("defaultTextColor");
CFStringRef colorBLUE = CFSTR("BLUE");
 
// Set up the preference.
CFPreferencesSetAppValue(textColorKey, colorBLUE,
        kCFPreferencesCurrentApplication);
 
// Write out the preference data.
CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication);

Notice that CFPreferencesSetAppValue by itself is not sufficient to create the new preference. A call to CFPreferencesAppSynchronize is required to actually save the value. If you are writing multiple preferences, it is more efficient to sync only once after the last value has been set than to sync after each individual value is set. For example, if you implement a preference pane you might synchronize only when the user presses an OK button. In other cases you might not want to sync at all until the app quits—although note that if the app crashes, all unsaved preferences settings will be lost.

Getting a Preference Value Using Core Foundation

The simplest way to locate and retrieve a preference value is to use the CFPreferencesCopyAppValue function. This call searches through the various preference domains in order until it finds the key you have specified. If a preference has been set in a less specific domain—Any Application, for example —its value is retrieved with this call if a more specific version cannot be found. Listing 2-3 shows how to retrieve the text color preference saved in Listing 2-2.

Listing 2-3  Reading a simple default

CFStringRef textColorKey = CFSTR("defaultTextColor");
CFStringRef textColor;
 
// Read the preference.
textColor = (CFStringRef)CFPreferencesCopyAppValue(textColorKey,
        kCFPreferencesCurrentApplication);
// When finished with value, you must release it
// CFRelease(textColor);

All values returned from preferences are immutable, even if you have just set the value using a mutable object.