Home > Articles > Programming > General Programming/Other Languages

Cocoa Tips: Don't Reimplement Standard Functionality

  • Print
  • + Share This
OS X comes with built in calendaring, a system-wide address book, a keychain for storing secrets, and a variety of other standard APIs. David Chisnall, author of Cocoa Programming Developer's Handbook, explains why using these, rather than rolling your own, will allow your program to work with all of the user's other programs.
From the author of

One of the most important rules in programming is that you should always try to avoid reinventing the wheel. Sometimes you don't have a choice; if you need a feature that isn't present, or if the existing wheel isn't fast enough, you might have to. Most of the time, however, it is more productive to use existing code.

On OS X, this is important from a user interface perspective, as well as for saving you some time. If you recreate some of the functionality that is included with OS X as standard, then it is likely to behave differently and not integrate well with other applications that use it.

There are a number of frameworks on OS X that provide default implementations of common features. These include things like the Keychain for storing secrets, the address book, for storing information about people, and calendar services for storing information about events.

There is nothing stopping you from providing your own code for implementing any of these, but it's usually a bad idea. For example, if your application needs to store information about people but you don't use the address book framework, your users will not be able to automatically sync those contacts with mobile devices using iSync. They will also not be able to access those contacts from the AddressBook app or programs like Mail.app that do integrate properly with the address book.

The same is true for other standard features on OS X. If two web browsers use the Keychain for storing passwords, the user can easily switch between them. This makes it much easier for users to try another browser, because all of their stored passwords continue to work.

System integration also makes it much easier to develop Mac applications. The more standard features you use, the less code you need to write. Writing less code means you can finish more quickly and usually means that you have fewer bugs — code that doesn't exist can't introduce new bugs. Of course, the standard Apple frameworks may contain bugs, but they are probably tested by more people than your code.

Storing Secrets

The Keychain API looks a bit weird to a Cocoa programmer. It comes from an old Apple mail program for Classic MacOS and inherits a lot of Pascal-style coding conventions. It doesn't, for example, use Cocoa or Core Foundation strings consistently. In several places that the API uses strings, it requires a length and a pointer, giving something like a Pascal string. All of the functions also return condition codes indicating whether they succeeded, just like the old Mac APIs.

The Keychain API is huge, but most applications will only need to use two of the functions: the ones for setting and retrieving a password. There are two variations of these functions, depending on whether your password is a generic password or an Internet password. They are similar, the only difference being that Internet passwords have a lot more metadata associated with them.

You create a generic password with this function:

OSStatus SecKeychainAddGenericPassword(
   SecKeychainRef keychain,
   UInt32 serviceNameLength, const char *serviceName,
   UInt32 accountNameLength, const char *accountName,
   UInt32 passwordLength, const void *passwordData,
   SecKeychainItemRef *itemRef);

The first argument is the keychain to use. Most of the time you will want the user's default keychain, so you can pass NULL in here. Some applications may wish to create private keychains, but that's quite unusual. The next two pairs of parameters describe the service and account. These are arbitrary strings which are used to uniquely identify the password. The next pair is the data for the password, and the final parameter allows you to get a pointer to the opaque type that describes the keychain object — most of the time you won't need to do this.

It's worth noting that it's very easy to make a mistake when you create the UTF-8 strings required for the components of this, by doing this:

// DON'T DO THIS!
OSStatus result = SecKeychainAddGenericPassword(
    NULL,
    [service length], [service UTF8String],
    [account length], [account UTF8String],
    [password length], [password UTF8String],
    NULL);

The -length method on NSString returns the number of characters in the string, while the parameter expects the number of bytes in the UTF-8 string. This is a difficult bug to spot, because it will work fine when the strings only contain 7-bit ASCII characters, but will suddenly break when they contain non-ASCII unicode characters. To make sure that you have the correct lengths, either use strlen() on the output from -UTF8String or use -dataUsingEncoding: and then pass the result of sending -length and -bytes to the returned data. Alternatively, you can use -lengthOfBytesUsingEncoding: on the original string. None of these is particularly efficient, but they will be cheaper than the keychain operations themselves.

Retrieving the password is then very simple, with a function that looks almost identical to the one used to save it.

OSStatus SecKeychainFindGenericPassword(
   CFTypeRef keychainOrArray,
   UInt32 serviceNameLength, const char *serviceName,
   UInt32 accountNameLength, const char *accountName,
   UInt32 *passwordLength, void **passwordData,
   SecKeychainItemRef *itemRef);

There are two differences between this and the first function. One is that the first parameter can now be a CFArray (NSArray) of keychains, rather than just one, if you want to search several places. The other is that the password-related parameters are now pointers and are used to output the password, rather than input it.

You can also control locking and unlocking keychains, and you may present your own dialog requesting the password for accessing the keychain. This is generally a bad idea. The keychain only grants applications access to individual passwords, and may only grant this access temporarily. The user only needs to trust your application with access to the specific password that it has requested in normal usage. If you ask for the password yourself, then the user must trust your application with complete access to the keychain and anything else protected by the password.

  • + Share This
  • 🔖 Save To Your Account