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.