Using the High-Level Preferences API

The functions CFPreferencesSetAppValue and CFPreferencesCopyAppValue are the most straightforward way for an application to create and retrieve a preference that is specific to the current user and application. The preference data is written to the default domain (Current User, Current Application, Any Host) and so it will be available on all machines that this user can log into. These functions should never be called with kCFPreferencesAnyApplication, only a true application ID or kCFPreferencesCurrentApplication.

Saving a Simple Preference

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 which defines the width in pixels of any new windows that your application 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 1 demonstrates how to create a simple preference for the application “MyTextEditor”. The example sets the default text color for the application to blue.

Listing 1  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 panel you might only synchronize when the user presses an “OK” button. In other cases you might not want to sync at all until the application quits—although note that, of course, if the application crashes all unsaved preferences settings will be lost.

Reading a Simple Preference.

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 will be retrieved with this call if a more specific version cannot be found. Listing 2 shows how to retrieve the text color preference saved in Listing 1.

Listing 2  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);

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

Updating a Simple Preference

An example of simple preference updating is a game that searches for a high score preference each time a round is completed. If there is no high score preference, the application writes the current score as the high score. If a high score preference exists, it is compared with the new score and updated if the new score is higher. Listing 3 demonstrates this process.

Listing 3  Updating a preference

CFStringRef highScoreKey = CFSTR("High Score");
CFNumberRef tempScore;
int highScore;
 
// Look for the preference.
tempScore = (CFNumberRef)CFPreferencesCopyAppValue(highScoreKey,
        kCFPreferencesCurrentApplication);
 
// If the preference exists, update it. If not, create it.
if (tempScore)
{
    // Numbers come out of preferences as CFNumber objects.
    if (!CFNumberGetValue(tempScore, kCFNumberIntType, &highScore)) {
        highScore = 0;
    }
    CFRelease(tempScore);
 
    printf("The old high score was %d.", highScore);
}
else
{
    // No previous value.
    printf("There is no old high score.");
    highScore = 0;
}
 
highScore += 5;
 
// Create the CFNumber to pass to the preference API.
tempScore = CFNumberCreate(NULL, kCFNumberIntType, &highScore);
 
// Set the preference value, or update it if it already exists.
CFPreferencesSetAppValue(highScoreKey, tempScore,
        kCFPreferencesCurrentApplication);
 
// Release the CFNumber.
CFRelease(tempScore);
 
// Write out the preferences.
CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication);

The technique shown in Listing 3 generalizes to context of multiple preferences where an application tries to locate a set of preferences for display to the user in a graphical preference panel. If no preferences exist, default values are used. If existing preference values are found, they are used to initialize the preference panel for display to the user. After the user makes changes and pushes the “OK” button, you can set the changed preference values and write them to storage.