A Safer KVC macro

January 08 2013

If you’re not familiar with the term, this just means instances where you’re using strings and a certain convention to identify methods that can be called on a given object. A particularly common instance of this is key-value observing such as -[NSObject addObserver:forKeyPath:options:context:], where you want to pass a key that you know is going to be valid and not return a runtime error.

A post got some attention recently promoting a way to statically-check key-value-coding methods. I’ve been using something along those lines for a while, but it has some important differences.

The disadvantage to the NSStringFromSelector() approach is that it doesn’t work in cases like hidden where the getter is called isHidden, since there is no valid selector called hidden. It also does not specify on which object the selector must be defined, so you could get away with mentioning a selector that is bogus for that object, but happens to be defined somewhere else (triggering a runtime error at some unknown time in the future).

My macro (which does support this usage) is as follows:

//o is an object type, k is the key on which to check validity
//returns a NSString constant of the relevant key

#define KVC(o, k) (0 && sizeof(o.k) ? @#k : @#k)

//Usage:
ObjectWithMethodCalledKeyName *object = ...;
[object addObserver:self forKeyPath:KVC(object, keyName) options:0 context:NULL];

Let’s dissect briefly when this will work and when it doesn’t. It uses the fact that you can do object.foo to call whatever the getter is for foo on object or just the method named foo if there is no property called foo. All great so far, but we don’t actually want to call this method, just find out statically whether it would be valid to call the method on an object of the type of object.

So, I use sizeof() as a way to invoke the compiler to consider the expression statically. Then, since I want it to return the key regardless of the actual result of sizeof(), avoid it being considered by a short-circuiting boolean operation and a ternary operator. The # stringifies k and the @ makes it an NSString.

Also available in gist!