Wednesday, September 15, 2010

tutorial: Key Value Coding done right

In MacOS X / Cocoa, there is this concept of "Key Value Coding". It allows you to loosely couple objects together in a flexible way. For example, this is used by NSCollectionView to glue the interface (the GUI) of the program to the actual code of the program.

Key Value Coding means that you can get the value of a property by its key (which is a string). Note that this is exactly what a NSDictionary does, so making your custom class work with KVC, is a lot like making it look like a NSDictionary.
If you are still confused about what KVC is and want to know more, you may read the excellent page on KVC at MacResearch.

Turning a class into a KVC class
To make a class KVC compliant, it has to respond to the -valueForKey: method. The quick and dirty way is to implement this by checking the string and returning nil if it's an unknown key. In many cases this will actually work. There are some little extras that you should know, though. They are best explained by example:
@interface Album : NSObject {
NSString *artist, *title;
}

@property (retain) NSString *artist, *title;

// in Album.m

@synthesize artist, title;

-(id)valueForKey:(NSString *)key {
if ([key isEqualToString:@"artist"]) {
if (artist == nil)
return [NSNull null];
return artist;
}
if ([key isEqualToString:@"title"]) {
if (title == nil)
return [NSNull null];
return title;
}
@throw [NSException exceptionWithName:NSUndefinedKeyException reason:[NSString stringWithFormat:@"key '%@' not found", key] userInfo:nil];
return nil;
}

-(NSArray *)allKeys {
return [NSArray arrayWithObjects:@"artist", @"title", nil];
}

-(NSArray *)allValues {
return [NSArray arrayWithObjects:artist, title, nil];
}

Concluding
When making your class KVC compliant, take care of the following:
  • implement -valueForKey: and check the name of every property
  • if the property is nil, return [NSNull null] to indicate that the value is nil
  • throw an NSUndefinedKeyException if the key is not found
  • implement -allKeys, returning an array containing all valid keys
  • implement -allValues, returning an array containing all values