Cocoa
KVC is quite simple: it’s a way to access an object’s members by their name, as a string, rather than accessing them directly – it’s comparable to Python’s getattr(); it has a clunkier syntax, but more features.
If you’re already familiar with Python’s getattr() you can probably skip down to Key Paths, but otherwise:
Let’s say you have a Colour class with three instance variables, red, green, and blue. Obviously these can be accessed in the usual way: myColour->red in C++, myColour.red in Python, [red](myColour) in Objective-C – all of these directly specify the red component, in code.
This can lead to unneccesary duplication of code. Say you have three “slider” UI widgets to allow the user to edit a colour; the code for all three is identical except for which component it sets. We’d like to just tell each slider which component it’s linked to and use the same code. In C/C++, we can do this quite easily with a switch, but it’s messy and awkward to maintain:
enum Components { redComponent, greenComponent, blueComponent };
void UpdateColourFromSlider(Colour* colour, Slider* slider)
{
switch(slider->colourToUpdate)
{
case redComponent: colour->red = slider->value; break;
case greenComponent: colour->green = slider->value; break;
case blueComponent: colour->blue = slider->value; break;
};
}
It works, but it’s verbose, and that’s assuming all the variables are public – most likely, you need to add a bunch of Get/Set method calls in there.
Then, we need to pretty much write all the same code again going the other way, to write an UpdateSliderFromColour() to make the linkage bi-directional – so that when you load a new colour up, the UI reflects it.
And if we add an alpha (opacity) component to the colour, we need to update not just the Colour class and the UI, but also the enum, and both update functions.
In Python, it’s oh so much simpler:
setattr(colour, slider.colourToUpdate, slider.value)
…where colourToUpdate is a string set to the appropriate name: "red", "green" or "blue". Yes, one line of code. No infrastructure to maintain.
Now, KVC allows us to do something almost as clean in Objective-C:
[colour setValue: [slider value] forKey: [slider colourToUpdate]];
Sweet. As you can see, it’s fractionally wordier, and the ordering has changed, but it’s basically the same as the Python.
You just can’t do this in C++. Oh, sure, you could write a method setValueForKey() on your Colour class, that would appear to work the same way. But inside, it would be essentially the same as UpdateColourFromSlider, except with a bunch of string compares. You’d still have to maintain it by hand. And you’d have to write one for every damn class, keeping it up to date with its members. Fundamentally, C++ has no way for code to figure these things out for itself. Python and Objective-C both Just Know(tm).
KVC is actually a bit more bionic than this, because it supports “Key Paths”. These are dot-separated lists of keys, each looked up on the result of the previous lookup. Let’s say you have a World object, with a Sky member, that contains an array “colours”, that it uses to render a gradient-fill on the horizon.
As well as asking the World object for the key sky, you could also ask it for the Key Path sky.colours, and it would return the appropriate array. Cocoa does need you to warn it when you do this: You look up valueForKeyPath: instead of just valueForKey:. You can layer these as deep as you like just by stacking up extra dotted names.
It gets more bionic still: If, in the example above, you looked up the key path sky.colours.blue, it would return an array of the blue values of each colour in the colours array. And if you looked up sky.colours.@avg.blue it would return a single value of the average blue value. Yes, Cocoa lets you slice members out of arrays, and put certain kinds of function into key paths. Yes, Cocoa is nuts.
Now, it’s important to understand how KVC finds these values. It searches in several places for a given name (searching for a key path obviously just splits the path on the dots, and looks up each key independently; so we’ll just look at regular keys now). Given a key sky, and assuming you’re trying to read (not write) the value, Cocoa looks for, in this order:
a getSky method that takes no arguments. If it finds one, it calls it, and returns the result.
the same for sky, and then isSky (as this naming convention is often used for bool member getters)
directly looking up sky or, failing that, _sky, as an instance variable – variables have a separate namespace from methods, in Objective-C (this step is optional and controlled by the class’ accessInstanceVariablesDirectly method)
and finally valueForUndefinedKey: – similarly to python’s __getattr__() customiser, this fallback allows you to define keys completely arbitrarily if you like.
Writing values is just the same, but with setSky and setValue:forUndefinedKey: at the appropriate places.
The take-away from all this is that it’ll just find the value automatically if you stick to standard Cocoa naming conventions. It’ll read data directly from the matching variable – unless you override it with getter/setter functions. The fact that you can map KVC to functions is particularly useful, as we’ll see in the next article.
Finally, it’s worth noting that with PyObjC, Python dictionaries gain KVC. So anywhere KVC is expected, you can pass a Python dict, and the keys will just be looked up there.
In the next part, we’ll look at the technologies that build on top of KVC.