Dan Wood: The Eponymous Weblog (Archives)

Dan Wood Dan Wood is co-owner of Karelia Software, creating programs for the Macintosh computer. He is the father of two kids, lives in the Bay Area of California USA, and prefers bicycles to cars. This site is his older weblog, which mostly covers geeky topics like Macs and Mac Programming. Go visit the current blog here.

Useful Tidbits and Egotistical Musings from Dan Wood

Categories: Business · Mac OS X · Cocoa Programming · General · All Categories

Thu, 28 Dec 2006

Storing Per-Machine Preferences

Occasionally the developer may want to store a preference in User Defaults which is only applicable to a particular machine, meaning that if the application is launched on a different machine, that preference will not be in effect. Imagine, for instance, a preference to show animation or not that is turned off on a slow machine, but then when the user upgrades to a fast machine and transfers her home directory, the old preference would no longer apply.

There is actually a way to do it somewhat elegantly in Cocoa, using [ScreenSaverDefaults defaultsForModuleWithName:], even if you're not writing a screen saver. The defaults are stored per-machine in ~/Library/Preferences/ByHost/, with the file name including the machine's MAC (Ethernet) address as part of the file name so that preferences are unique to each machine.

We tried using this approach in Sandvox, but found it tricky to store some defaults there and others in [NSUserDefaults standardUserDefaults]. So instead we came up with a new, simple approach: use the MAC address as part of the defaults key.

For instance, we have a preference to remember if Core Image is accelerated on the particular machine. (The test for this is a bit of a lenghty process, so it's best to perform it once when needed and store the result in user defaults for subsequent launches.) Rather than writing the preference under the key of "CoreImageAccelerated", we construct the key like this:

NSString *key = [NSString stringWithFormat:@"CoreImageAccelerated %@",
    [[KTUtilities MACAddress] base64Encoding]];

As with the screen saver defaults, we use the MAC address. Alas, there is no easy way to get the MAC address of your Mac, but Apple has provided some decent sample code to get started on that method; I'll leave that as an exercise to the reader.

We happen to have some methods to encode and decode in Base 64; it's a nice compact way to represent binary data as an ASCII string. So we take the NSData returned by MacAddress and convert it into a string. I won't go into the implementation details, but it's just a few lines of code wrapped around some library functions in curl, which every Mac has. Base 64 encoding, for example, uses the function Curl_base64_encode.

Each time the application is launched from the same machine, the key will remain constant. If the user defaults are tranferred to another machine, then the value will be reset for that machine. (The old key will remain in the defaults file, which might be useful if you have a shared home directory, where the same home directory and defaults file is used but from different machines.)