Cocoa for Scientists (Part XI): The Value in Keys

Author: Drew McCormack
Website: http://www.maccoremac.com

In the coming tutorials, I plan to introduce Cocoa Bindings and contrast it with the ‘traditional’ outlet/action approach to app development. But in order to understand how Bindings work, you first need to know about the underlying Cocoa technologies, namely, Key-Value Coding (KVC) and Key-Value Observing (KVO). In this short tutorial, I will introduce you to the marvels of keys and key paths, which underpin much of the Cocoa coolness. Next time, I will take a look at Key-Value Observing.

Key-Value Coding

Key-Value Coding (KVC) is actually not a new technology at all — it was there long before Mac OS X was even a twinkle in Steve Jobs’ eye. KVC leverages the dynamicism of Objective-C to allow you to access the properties of an object using strings to identify them. Usually when you want to retrieve a property, you would call a getter accessor, like this

float height = [object height];

This ‘hard-coded’ call is not that flexible. For example, what you if you don’t know which method you want to call at compile time? Maybe you determine the method from some runtime data.

KVC allows you to get or set a property with only a string for identification. For example, the invocation above could be replaced by this:

NSNumber *number = [object valueForKey:@"height"];
float height = [number floatValue];

or more concisely

float height = [[object valueForKey:@"height"] floatValue];

This may seem like a step back in terms of legibility, but it is a big step forward in flexibility, and many of Cocoa’s coolest features are only made possible by this flexibility.

So what is happening when you invoke the valueForKey: method (which, incidentally, derives from the NSObject class)? The Objective-C runtime goes through the methods that belong to the object in question, and looks for a getter with the name height or getHeight. If it finds one, it invokes it, returning the result. If the result is a simple type, like a float or int, it first converts it into an NSNumber object before returning it.

What happens if there is no method called height? Then the runtime will look at the instance variables belonging to the object, and see if one exists called height or _height. If it finds one, it will return its value directly.

There are actually strict rules about how the variables and methods can be named, and the order in which they are sought, but you don’t really need to know them unless you want to get funky with variable names (…and you shouldn’t get funky with variable names).

The flip side of getting a value is setting a value. The KVC setter is called setValue:forKey:. It works the same as for getters, but first tries to call a set...: method, before setting the value directly. Here is an example of setting the height attribute:

[object setValue:[NSNumber numberWithFloat:180.0] forKey:@"height"];

This will look for a method called setHeight: and invoke it if found. KVC is smart enough to see that setHeight: expects to get a float argument, and that the NSNumber you have passed to setValue:forKey: needs to be transformed before being passed in. If there is no setHeight: method, the runtime system will proceed to look for a height or _height instance variable (ivar), and set that directly. When setting variables directly, KVC will even handle memory management, releasing any existing value, and retaining the new value, where appropriate.

Key Paths

KVC starts to get much more interesting when you learn that it isn’t restricted to simple keys, but can take key paths. A key path is a bit like a path in the file system, but for objects rather than files and directories. The directory path /Users/cormack/Library/Application Support gives the location of my Application Support directory relative to the root of the file system. The same trick works with KVC:

int numAtoms = [[chemicalSystem valueForKeyPath:@"molecule.numberOfAtoms"] intValue];

The invocation of valueForKeyPath: looks for an object at the path molecule.numberOfAtoms relative to the ‘root’ object chemicalSystem. The path delimiter is a point, rather than a forward slash, but the concept is the same.

So what will this code do? The Objective-C runtime will first use KVC to retrieve an object for the attribute molecule. It will use the same rules discussed earlier, first looking to see if there is an accessor method (eg molecule or getMolecule), and if not trying direct access to the ivar. When it has retrieved the object corresponding to the molecule, it will use KVC to retrieve the numberOfAtoms attribute of that object. This NSNumber object is what gets returned from valueForKeyPath:.

Not surprisingly, the same trick can be used for setting an attribute:

[chemicalSystem setValue:[NSNumber numberWithInt:10] forKeyPath:@"molecule.numberOfAtoms"];

Before moving on, it is also interesting to note that KVC is a form of intelligent high-level messaging. We have already seen that it knows how to change the arguments and return values of functions, and that it can follow a path through an object graph, but it can do even more. For example, you can retrieve whole arrays of objects:

NSArray *atomicMasses = [chemicalSystem valueForKeyPath:@"molecule.atoms.mass"];

This assumes the following class interfaces:

@interface ChemicalSystem : NSObject {
    Molecule *molecule;
}
-(Molecule *)molecule;
@end

@interface Molecule : NSObject {
    NSArray *atoms;
}
-(NSArray *)atoms;
@end

@interface Atom : NSObject {
    float mass;
}
-(float)mass;
@end

KVC is smart enough to see that the atoms attribute in the Molecule class is an array, and it will loop over the array extracting the mass for each object in it. The NSArray returned will contain NSNumbers representing the atomic masses of each of the atoms in the chemical system. Without KVC, you would need to write a loop and many more lines of code:

NSMutableArray *atomicMasses = [NSMutableArray array];
Molecule *molecule = [chemicalSystem molecule];
NSArray *atoms = [molecule atoms];
int i;
for ( i = 0; i < [atoms count]; ++i ) {
    Atom *atom = [atoms objectAtIndex:i];
    [atomicMasses addObject:[NSNumber numberWithFloat:[atom mass]]];
}

Cocoa is All About Conventions

The more you work with Cocoa, the more you realize that it is a convention-based system. Traditionally, programming languages and APIs have been designed to force you to declare things up front. If you are going to call a method at some point in your code, the compiler needs to know what method that is, or at least that it exists. This approach could be summarized as the ‘configuration’ approach to development. You have to configure everything explicitly before your software runs. By its nature, it is an inflexible approach.

The trend lately — in new web frameworks like Ruby On Rails, for example — is to favor a convention-based approach. Rather than requiring the developer to explicitly define all relationships in the software, newer frameworks and languages tend to define implicit conventions. This can dramatically reduce the amount of work you have to do, and the resulting software is often much more amenable to change.

Cocoa could be considered one of the ancestors of these cool new languages/APIs — KVC is a convention-based approach to development. By sticking to the Cocoa accessor naming conventions, and thereby allowing relationships to be defined implicitly, you are saving yourself a lot of work, and ultimately the software is more supple to change.

Next Stop: KVO

Next time we will take a look at the flip size of KVC: Key-Value Observing (KVO). This is used not to get and set attributes, but to observe when they have changed. After that, we will be ready to step into Cocoa Bindings, which allows you to connect up your user interface to your model data using the key paths that I started introducing today.

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Cool keys!

KVC looks really convenient. it'll be a bit to digest until your next tut. thanks!

NSMutableDictionary

I was just wondering, suppose I have some calculation parameters. Is it better to put them in a dictionary and use valueForKey: or objectForKey: to retrive them instead of many int, or floats?

Also, one thing I'm not so sure of. What's setValue: forKey: for in NSMutableDictionary. How is it different to setObject: forKey:? I know I can do setObject:[NSNumber numberWithInt:4] forKey:@"four", but I can't setValue:4 forKey:@"four" right?

Thanks
ken

Re: NSMutableDictionary

>> What's setValue: forKey: for in NSMutableDictionary. How is it different to setObject: forKey:?

setValue: forKey: checks to see whether value is nil before using
setObject: forKey: , according to this
Apple documentation. When the value is nil, setValue: forKey: "attempts to remove key using removeObjectForKey:"(Apple documentation)

>>I was just wondering, suppose I have some calculation parameters. Is it better to put them in a dictionary and use valueForKey: or objectForKey: to retrive them instead of many int, or floats?

It depends. Sometimes an NSArray or NSMutableArray is enough. If that is slow, you can use NSDictionary. NSArrays are "toll-free bridged" to CF(Core Foundation) Arrays, according to Apple. According to this unofficial article, CFArrays actually switch implementations when the number of items gets beyond a certain point, so that NSArray(and CFArray) lookups are not much slower than NSDictionary lookups when the number of items in the array is 500,000 or more(again, this is from an unofficial source). The point is, arrays might suit your purposes too.

And sometimes it is just what you prefer. Maybe you prefer the NSDictionary or NSMutableDictionary way of doing things.

>> I know I can do setObject:[NSNumber numberWithInt:4] forKey:@"four", but I can't setValue:4 forKey:@"four" right?

Right, because setValue:4 forKey:@"four" tries to send setObject:4 forKey:@"four" behind the scenes after it checks for nil value, so that doesn't work.

re: NSMutableDictionary

Thanks. So in effect, setValue: forKey: is kinda like setObject: forKey: and removeObjectForKey: tied into one with the use of nil. One reason why I would like to use NSDictionary/NSMutableDictionary is because I can get to label them and call them by their keys instead of remembering which index of the array they are in. This is one of the advantages of key value coding?

Thanks
ken

NSMutableDictionary

It depends on what the parameters are that you are storing. If you are storing thousands of values, and need to perform expensive operations on them, stick to a C struct/array, perhaps in combination with NSData.

If there is not too much data, and operations are not expensive, use your own class (with Key-Value Coding) or NSDictionary. I would generally favor a class unless it is just a temporary dictionary of data. Classes make program design a bit clearer, and you can access them like a dictionary using key-value coding anyway.

The setValue:forKey: in NSMutableDictionary is there to support key-value coding, and does the same thing as setObject:forKey: Use either.

Lastly, whenever you use key-value coding, you need to pass in objects, and you get back objects. You can't use simple types. So you have to use an NSNumber, not an integer.

Drew

---------------------------
Drew McCormack
http://www.maccoremac.com
http://www.macanics.net
http://www.macresearch.org

Re: NSMutableDictionary

@Drew: Lastly, whenever you use key-value coding, you need to pass in objects, and you get back objects. You can't use simple types. So you have to use an NSNumber, not an integer.

I forgot to say this, good thing Drew said it. Xcode also gives you warning if you try to use simple types with key-value coding.

@Drew:If you are storing thousands of values, and need to perform expensive operations on them, stick to a C struct/array, perhaps in combination with NSData.

Like Drew says, C structs/arrays are the most efficient for expensive operations. I would just add that you would have to be careful if you are using C arrays to check for out-of-range indexing.

Classes and routines

Maybe a trival question. But if I have subroutines that calculate some parameters in fortran or c. What would be an equivalent in OOP? Would it be a method in a class or a separate class of its own?

Thanks
ken

Re: Classes and routines

It really depends on the routine. Some would be left as functions/subroutines; some might become methods in a class; and in some cases, it may be appropriate to turn certain functions into classes with a method. You really can't give a general answer.

Drew

---------------------------
Drew McCormack
http://www.maccoremac.com
http://www.macanics.net
http://www.macresearch.org

convenience constructors

thanks. hope you don't mind i pop another question. about convenience constructors. how would i write a method that creates one? Suppose i have

- (id)initWithCoordinates:(NSArray *)coords
{
if(self = [super init])
{
[self setX:[[coords objectAtIndex:0] floatValue]] ;
[self setY:[[coords objectAtIndex:1] floatValue]] ;
}
return self;
}

if i want something along the lines of


- (id)positionWithCoordinates:(NSArray *)coords
{
if(self = [super init])
{
[self setX:[[coords objectAtIndex:0] floatValue]] ;
[self setY:[[coords objectAtIndex:1] floatValue]] ;
}
[self autorelease] ;
return self;
}

is that right? thanks for putting up with my questions
ken

re: convenience constructors

Yes, the last snippet of code is right with the autorelease, except I think you should make this a class method because "Many classes provide methods of the form +className... that you can use to obtain a new instance of the class. Often referred to as “convenience constructors”, these methods create a new instance of the class, initialize it, and return it for you to use." -- from this Apple documentation.
You might do something like this(the only change is the + here).

+ (id)positionWithCoordinates:(NSArray *)coords
{
if(self = [super init])
{
[self setX:[[coords objectAtIndex:0] floatValue]] ;
[self setY:[[coords objectAtIndex:1] floatValue]] ;
}
[self autorelease] ;
return self;
}
You would also create objects differently. For example using the NSString class with a convenience constructor, you would have the following syntax:

NSString *str = [NSString stringWithString:@"string"];

..and you could adapt the syntax to your specific scenario.

Drew would be much more familiar with this topic than I am though...

Regards,

Chinmoy

Re: Convenience Constructors

As Chinmoy says, a convenience constructor has to be a class method. Another problem with your code is that in a class method, the self pointer is actually to the class, not an object. You need to alloc a new object, and return that:

+ (id)positionWithCoordinates:(NSArray *)coords 
{
    id newObject;
    if ( newObject = [[[self class] alloc] init] ) {
        [newObject setX:[[coords objectAtIndex:0] floatValue]] ;
        [newObject setY:[[coords objectAtIndex:1] floatValue]] ;
    }
    return [self autorelease] ;
}

Note that I use [self class] to access the class, rather than referring to it directly with the class name (ie Position). That way, it may be possible to use this same convenience constructor from subclasses of Position, because [self class] is always the actual class that the method belongs to.

Drew

---------------------------
Drew McCormack
http://www.maccoremac.com
http://www.macanics.net
http://www.macresearch.org