Fun with KVC

[Warning: this article is for entertainment purposes only. Any harm you do to your code or any offense you incur on the sensibilities of other programmers is your responsibility.]

Some time ago, Guy English posted a great and twisted article on key value coding (KVC). He comes up with a nice hack to do stuff with KVC that was never intended. Of course, there are some neat ways you can use KVC without indulging in the craziness that Guy English delves into (and I think he’d be the first to admit that it’s not meant for primetime). The main point remains, which is that using KVC with NSArrays is a great way to avoid coding loops over their contents. You have an array of “Person” objects and want an array of their names (accessible via a -name method)? Just do…

newArray = [array valueForKey:@"name"];

Now, the keys do not necessarily have to be properties of the object. There’s nothing stopping you from calling other methods on your objects that return other objects, provided they don’t require any arguments. For instance, if starting out with an array of strings…

newArray = [array valueForKey:@"lowercaseString"]

…will generate a new array of lowercase versions of the original strings. Actually, the methods don’t even have to return objects. Want to convert an array of numbers in string form to numbers? Try the following:


array = [NSArray arrayWithObjects:@"1", @"2", @"3", @"4", nil];

newArray = [array valueForKey:@"intValue"];

This gives you an array of NSNumbers. KVC auto-boxes scalar types as needed. It’s a great way to do conversions/transformations of arrays of objects in one fell swoop.

Suppose you want to do a deep(er) copy of an array of objects (not only copy the array, but each of its objects as well):

newArray = [array valueForKey:@"copy"];

There is the issue, though, that the copies in the array now have a retain count of 1 and will be leaked unless extra care is taken. But no need write a loop to autorelease the objects. Instead, we can use keypaths to create chains:

newArray = [array valueForKeyPath:@"copy.autorelease"];

Retain it now and release it later or just let it get autoreleased. Either way, everything cleans up nicely.

And if you have a bunch of NSMutableCopying-conformant, immutable objects and want to convert them to their mutable counterparts:

newArray = [array valueForKeyPath:@"mutableCopy.autorelease"];

That gives us an array of autoreleased mutable copies.

Sure, not super mind-blowing but hopefully something above may save you some keystrokes. If you have your own KVC tricks, post them here in the comments.

[Update 2009-06-30]

This all started out with an conversation I had with Guy about wacky KVC stuff. The original title of this post was “KVC Abuse” but after a few edits, it got lost. I added the warning at the top but I’ll make it clear here: this is an abuse of KVC. This whole post is an indulgence.

I avoided a whole discussion of implementing HOM (Higher Order Messaging) type functionality in Objective-C but here’s a method you can use in an NSArray category:

- (NSArray *)map:(SEL)selector
{
	NSMutableArray		*result;
	
	result = [NSMutableArray arrayWithCapacity:[self count]];
	for (id object in self)
	{
		[result addObject:[object performSelector:selector]];
	}
	return result;
}


You don’t get the auto-boxing or the keypath stuff but the end result is still succinct and convenient for the basic case:


newArray = [array map:@selector(lowercaseString)];

Of course, my own version that I use has a more verbose method name (-transformedObjectsUsingSelector:) but no matter how you slice it, it will generate less bile from other developers.

Category: Cocoa, OS X, Programming 6 comments »

6 Responses to “Fun with KVC”

  1. Guy

    NSArray *classes = [NSArray arrayWithObjects: [NSObject class], [NSObject class], [NSObject class], nil];

    NSArray *instances = [classes valueForKeyPath: @"alloc.init.autorelease"];

    I think Mike Ash showed me that first. He actually had a reason (well … close enough) to want to do it but I forget what it was. And, yeah, my KVC stuff is more for the sake of interest than hard core use. Or, at least, don’t swizzle valueForKeyPath: to do it but add another method that’ll parse the queries. (and I really should fix up my blog …)

    - Guy

  2. Paul

    What you’re pointing out is that KVC (in this case) is actually a form of ‘map’ (from functional idioms).

    Now I’m not sure I’d actually want to use it as such, but I’ve certainly missed map and it’s cohorts at times. I think I’d rather have a version that was intended for the purpose though, and didn’t have the possibility of working differently at some point in the future.

  3. Aaron Burghardt

    Not KVC, but another great option to avoid manually iterating over a collection is NSArray’s and NSSet’s makeObjectsPerformSelector: and makeObjectsPerformSelector:withObject:. So, you could:

    [classes makeObjectsPerformSelector:@selector(@"autorelease")];

    or

    [people makeObjectsPerformSelector:@selector(@"setCompany:") withObject:@"Apple, Inc."];

    [people makeObjectsPerformSelector:@selector(@"setDepartment:") withObject:dept];

    The first example isn’t as short as your KVC form, but feels less like it’s abusing the frameworks. It’s also probably more efficient.

  4. mr_noodle

    Guy: I was going to post your alloc/init example as well but couldn’t recall where it would be used.

    Paul/Aaron: I updated my post to clarify that this was much more of a random tangent. I also posted a more kosher way of doing a map function on an array.

    Aaron: -makeObjectsPerformSelector here won’t work for doing a map-like function where you need to collect the return values of the method call. It is the natural and correct replacement for abuse of -setValue:forKey: though.

  5. Aaron Burghardt

    Right, I didn’t mean to imply it was equivalent, just sharing a nice complement that is useful for similar situations and something I would actually use. Your point that the post was an indulgence wasn’t lost. I use KVC on arrays regularly, but you had some neat tricks. Overall, a great read!

  6. Kirk Kerekes

    I would dispute the contention that these techniques are “abuse”, indeed I feel that they are now central to efficient, maintainable coding in Cocoa, and their virtuousness is implied by:

    1. The very nature of object properties/accessors.
    2. ObjC’s “Duck Typing”
    3. The existence of the KVC array and set “operators”
    4. The “@” prefix KVC override on the NS collection class clusters

    BTW: makeObjectsPerformSelector:withObject: where the second param is a mutable collection is a fine way to get the return values of a method call. It can be much faster (but much less obvious) than more conventional strategies for binning/indexing.

    Guy English’s extensions to the concept are intriguing, but swizzling the behavior of KVC seems like a pretty hazardous notion for production code.


Leave a Reply



Back to top