Using NeXTstep's Defaults System

by Ray Ryan

If you've written an application for a user base of more than one, you've probably felt the need to allow some kind of customization. You may have called it providing preferences, or parameters, or defaults, or something else but what you were thinking about was giving users a say in how the program worked after they installed it.

This is a common need, and to help meet it, NeXTstep provides the defaults database, a dual-key database of small text values, in each user's home directory. Defaults provide a way for applications to keep their preference and configuration settings in a central place. It keeps the user's home directory from becoming a mess of dot files and hidden directories, each one written with its own application-specific format.

How does it work?
The defaults system stores values with two keys, an owner and a name. The owner is usually the name of the application that stored the value; the name describes what the value means. The database is stored in two files kept in the user's ~/.NeXT directory you should never touch those files directly. There are perfectly good function calls for that.

The most obvious use of the defaults database is with your Preferences panel, but this is by no means the only place it's handy. Any value that should be stored on a per-user basis (but that doesn't make sense as part of a document) can probably find its way into the defaults system. One common use is to keep track of an application's panels between sessions: their size and position, and whether the user left them open or closed.

The general question of what belongs in a document and what belongs in the database isn't always straightforward. It's pretty obvious that Emacs key bindings being enabled or disabled should be a preference. When I mail you a document that I've been working on, do you really want to be subject to my control-key preferences? But what about the grid-snapping features of most drawing packages? Different apps have it stored in different places, and what makes sense to me (in the document) may seem like an incredible leap in illogic to you.

Registering your defaults
In the simplest case, there are three basic steps to using defaults from an application. Soon after launching, call NXRegisterDefaults() with an NXDefaultsVector to tell NeXTstep who you are, and the names of the defaults you want to use. When you're actually ready to use a value, fetch it from the database with NXGetDefaultValue(). Use NXWriteDefault() to write them back

. In code, the registration step can look something like this:

	+ initialize
	const NXDefaultsVector MyDefaults = {
	{"AutoSave", "NO"},
	{"NXFont", "Helvetica"},

	NXRegisterDefaults([NXApp appName], MyDefaults);
	return self;
This might be the +initialize method of the class that uses these defaults. Or you could put similar code into your application delegate's -appDidInit: method. Or both. It's perfectly acceptable to call NXRegisterDefaults() more than once.

The "NXDefaultsVector MyDefaults" is a list of name/default value pairs. Note that it ends with "{NULL, NULL}."

When you call NXRegisterDefaults(), the system checks to see if each default you name was specified on the command line (for example, with -AutoSave YES). If not, the system checks the database, first with the owner you provide (in this case "MyApp"), then with owner GLOBAL. If all of these checks fail, the default is created with the value you provide.

Wherever the value finally comes from, it's kept in memory for quick access when you look for it with NXGetDefaultValue().

The NXFont default value is a special one, as are most with the NX prefix. (You should never create your own default value with an NX prefix that's reserved for NeXT.) NXFont is the value set by the Application Font section of the Preferences app. Text objects look for this value on their own, without you having to lift a finger. If you're using text objects and you want to provide a default-font preference, just store a value with owner "MyApp," and name "NXFont." (Text objects also look for the NXFontSize default.) You should add your own prefix to your default names to avoid clashes with other apps.

Reading defaults from the database
When you're ready to use, say, the auto-save value, you might do something like this:

if ([self itsAutoSaveTime] && 	
	 !strcmp(NXGetDefaultValue([NXApp appName],"AutoSave"), "YES"))
	[[NXApp delegate] save];
(Remember that strcmp() returns zero for strings that match.) NXGetDefault() looks for a value for "AutoSave" with owner "MyApp" first in memory, then in the on-disk database. If that fails (for example, if you hadn't included "AutoSave" when you called NXRegisterDefaults()), it looks under the owner GLOBAL.

Writing out new values for your defaults is straightforward. The simplest thing to do is write out a default as soon as it's changed. For example, the target/action method for the auto-save check box on your Preferences panel might look like this:

	- setAutoSave:sender
	NXWriteDefault([NXApp appName], [sender state] ? "YES" : "NO");
	return self;
This updates the MyApp AutoSave value in the memory structure we keep mentioning, and writes it to disk as well.

In addition to the programmer's interface, NeXTstep provides three command line programs dread, dwrite, and dremove that give you access to the defaults system. To see all of your defaults, type:

localhost> dread -l
You can also read a specific value, like:

localhost> dread MyApp AutoSave
To see all of the values for a particular owner, type:

localhost> dread -o MyApp
The command dwrite works similarly. Setting a value is as simple as:

myhost> dwrite MyApp AutoSave YES
You don't have to resort to the command line, either. Floating around the net archives is Marc Davidson's excellent DefaultMgr. This NeXTstep app provides a browser interface to the defaults system. It gives you complete control over existing values, and lets you create and destroy owners and names with menu commands, exactly as you'd expect.

How shouldn't I use it?
You can't go bananas with what you store in the defaults system; it's not a big database manager. You can only fit 1023 characters into a value. And remember, it's for character strings only, not data streams.

And don't expect your users to know anything about dread and dwrite, or about DefaultMgr. If you expect real use to be made of values you store in the defaults system, give people a good way to get at them.

Ray Ryan is one of the founders of Lighthouse Design.