Bill Bumgarner

2004-1-18

Key/Value Coding & Classes

Key/Value coding is a large part of what makes Enterprise Objects, WebObjects and the Cocoa Bindings so elegant and consistent while requiring very little code.

I have often wanted to be able to start a key path at a class. By doing so, it would allow one to express key paths that could retrieve user defaults, use the app's main bundle, retrieve anything hanging anywhere on a path reachable from the shared NSApplication instance, or retrieve or set any other attribute found via a shared instance of one of the various manager classes. It would also allow one to express any of the standard colors as a key path.

Extending the evaulation of key/value paths themselves would be easy, but doing so would have an unfortunate performance impact. Since key/value paths always start at some random subclass of NSObject, the alternative is to simply add a method to NSObject that returns an object that knows how to evaluate the next element in the key/path as a class name.

Adding this functionality is trivial. The code below can be added to any Cocoa our Foundation project. This project contains the code below and some example usages contained within the NIB file using the Controller layer. It displays two windows. The first window contains the identifiers and paths of all frameworks linked into the application using an Array Controller with its contentArray binding bound to the path keyValueClassCoding.NSBundle.allFrameworks. Another window displays all font families.

#import <Foundation/Foundation.h>

@interface ClassCoder:NSObject
@end

@implementation NSObject (ClassCoderAccess)
- keyValueClassCoding
{
    static ClassCoder *classCoder = nil;
    if (!classCoder)
        classCoder = [[ClassCoder alloc] init];
    return classCoder;
}
@end

@implementation ClassCoder
- valueForKey: (NSString *) aKey
{
    return NSClassFromString(aKey);
}
@end
Update: Oops. I screwed up. +intialize won't work in categories. For some reason, I had latched onto the notion that it worked like +load in that it would be invoked per category as well as per class. As it turns out, multiple +initialize implementations per class (loaded via categories) behaves in a non-deterministic fashion. Thanks to Mike Ferris for kindly pointing out my error. The above code will work much better an the zip file linked above has been updated. I chose not to use +load because it is relatively evil in that it is invoked well before main() and there is zero control over order of invocation. In this case, I could most likely get away with it, but I would rather have "100%" working over "most likely" working. If the extra cycle used to if (!classCoder) bothers you, there are a couple of solutions including one truly evil solution involving IMP swizzling (but still using public API).

Comment on this post [ so far] ... more like this: [Code, Mac OS X] ... topic exchange: [Code]

Single Board Birdhouse

Just built three single board birdhouses. Easy and I can use the excuse that my three year old son helped me to explain the slightly odd angles.

I'm a better coder than woodworker and that says little about the quality of my code.

Comment on this post [ so far] ... more like this: [Life]