patrick patrick - 2 months ago 17
Objective-C Question

Is there a way to get all the first objects of an array of arrays with KVC?

Say I have an array of arrays like:

NSArray *array = @[@[@1, @2], @[@3, @4], @[@5, @6]];

Is there a KVC key path that would give me @[@1, @3, @5] ?


To my surprise, there is, although it relies on some undocumented behavior.

The documentation for NSArray's override of -valueForKey: says it builds a new array by applying -valueForKey: to each element with the provided key and returns that. It does not suggest that any keys are special.

However, NSDictionary's override does say that keys starting with @ are treated specially. Instead of looking at the contents of the dictionary, it looks at the properties of the dictionary itself for one matching the key stripped of the leading @. So, you could use [someDict valueForKey:@"@count"] to get the count of objects in the dictionary.

As it turns out, NSArray actually respects the same convention. You can use [array valueForKey:@"@firstObject"] to get @[@[@1, @2]] in your example. Now the question is, how do you get that to apply to the inner arrays, not the outer array.

First, what happens if you just call [array valueForKey:@"firstObject"] without the @ on the key? The outer array calls [innerArray valueForKey:@"firstObject"] on each of the inner arrays. Each inner array, in turn, tries to call [element valueForKey:@"firstObject"] on each of its elements. However, its elements are NSNumber objects which aren't KVC-compliant for the key "firstObject". So that blows up.

But, KVC supports collection operators with -valueForKeyPath: (note the "Path" on the end of that). One operator is @unionOfObjects. This applies the part of the path to the right of the operator to the elements of the array and then returns an array of the results. This is similar to the case where -valueForKey: propagates to the inner arrays, except you get to specify a different key for the inner arrays.

So, combining these mechanisms, you can do: [array valueForKeyPath:@"@unionOfObjects.@firstObject"] to get @[@1, @3, @5]. The outer array applies [innerArray valueForKeyPath:@"@firstObject"] to each inner array. Because of the leading @, that does not propagate to the elements of the inner arrays. It just gets the firstObject of the inner arrays. Then the outer array combines those into a result array.