KVC Collection Operators
Rubyists laugh at Objective-C’s bloated syntax.
Although we lost a few pounds over the summer with our sleek new object literals, those Red-headed bullies still taunt us with their map
one-liners and their fancy Symbol#to_proc
.
Really, a lot of how elegant (or clever) a language is comes down to how well it avoids loops. for
, while
; even fast enumeration expressions are a drag. No matter how you sugar-coat them, loops will be a block of code that does something that is much simpler to describe in natural language.
“get me the average salary of all of the employees in this array”, versus…
double total Salary = 0.0;
for (Employee *employee in employees) {
total Salary += [employee.salary double Value];
}
double average Salary = total Salary / [employees count];
Meh.
Fortunately, Key-Value Coding gives us a much more concise–almost Ruby-like–way to do this:
[employees value For Key Path:@"@avg.salary"];
KVC Collection Operators allows actions to be performed on a collection using key path notation in value
. Any time you see @
in a key path, it denotes a particular aggregate function whose result can be returned or chained, just like any other key path.
Collection Operators fall into one of three different categories, according to the kind of value they return:
- Simple Collection Operators return strings, numbers, or dates, depending on the operator.
- Object Operators return an array.
- Array and Set Operators return an array or set, depending on the operator.
The best way to understand how these work is to see them in action. Consider a Product
class, and a products
array with the following data:
@interface Product : NSObject
@property NSString *name;
@property double price;
@property NSDate *launched On;
@end
Key-Value Coding automatically boxes and un-boxes scalars into
NSNumber
orNSValue
as necessary to make everything work.
Name | Price | Launch Date |
---|---|---|
iPhone 5 | $199 | September 21, 2012 |
iPad Mini | $329 | November 2, 2012 |
MacBook Pro | $1699 | June 11, 2012 |
iMac | $1299 | November 2, 2012 |
Simple Collection Operators
-
@count
: Returns the number of objects in the collection as anNSNumber
. -
@sum
: Converts each object in the collection to adouble
, computes the sum, and returns the sum as anNSNumber
. -
@avg
: Takes thedouble
value of each object in the collection, and returns the average value as anNSNumber
. -
@max
: Determines the maximum value usingcompare:
. Objects must support comparison with one another for this to work. -
@min
: Same as@max
, but returns the minimum value in the collection.
Example:
[products value For Key Path:@"@count"]; // 4
[products value For Key Path:@"@sum.price"]; // 3526.00
[products value For Key Path:@"@avg.price"]; // 881.50
[products value For Key Path:@"@max.price"]; // 1699.00
[products value For Key Path:@"@min.launched On"]; // June 11, 2012
Object Operators
Let’s say we have an inventory
array, representing the current stock of our local Apple store (which is running low on iPad Mini, and doesn’t have the new iMac, which hasn’t shipped yet):
NSArray *inventory = @[i Phone5, i Phone5, i Phone5, i Pad Mini, mac Book Pro, mac Book Pro];
-
@union
/Of Objects @distinct
: Returns an array of the objects in the property specified in the key path to the right of the operator.Union Of Objects @distinct
removes duplicates, whereasUnion Of Objects @union
does not.Of Objects
Example:
[inventory value For Key Path:@"@union Of Objects.name"]; // "i Phone 5", "i Phone 5", "i Phone 5", "i Pad Mini", "Mac Book Pro", "Mac Book Pro"
[inventory value For Key Path:@"@distinct Union Of Objects.name"]; // "i Phone 5", "i Pad Mini", "Mac Book Pro"
Array and Set Operators
Array and Set Operators are similar to Object Operators, but they work on collections of NSArray
and NSSet
.
This would be useful if we were to, for example, compare the inventory of several stores, say apple
, (same as in the previous example) and verizon
(which sells iPhone 5 and iPad Mini, and has both in stock).
-
@distinct
/Union Of Arrays @union
: Returns an array containing the combined values of each array in the collection, as specified by the key path to the right of the operator. As you’d expect, theOf Arrays distinct
version removes duplicate values. -
@distinct
: Similar toUnion Of Sets @distinct
, but it expects anUnion Of Arrays NSSet
containingNSSet
objects, and returns anNSSet
. Because sets can’t contain duplicate values anyway, there is only thedistinct
operator.
Example:
[@[apple Store Inventory, verizon Store Inventory] value For Key Path:@"@distinct Union Of Arrays.name"]; // "i Phone 5", "i Pad Mini", "Mac Book Pro"
This is Probably a Terrible Idea
Curiously, Apple’s documentation on KVC collection operators goes out of its way to make the following point:
Note: It is not currently possible to define your own collection operators.
This makes sense to spell out, since that’s what most people are thinking about once they see collection operators for the first time.
However, as it turns out, it is actually possible, with a little help from our friend, objc/runtime
.
Guy English has a pretty amazing post wherein he swizzles value
to parse a custom-defined DSL, which extends the existing offerings to interesting effect:
NSArray *names = [all Employees value For Key Path: @"[collect].{days Off<10}.name"];
This code would get the names of anyone who has taken fewer than 10 days off (to remind them to take a vacation, no doubt!).
Or, taken to a ridiculous extreme:
NSArray *album Covers = [records value For Key Path:@"[collect].{artist like 'Bon Iver'}.<NSUnarchive From Data Transformer Name>.album Cover Image Data"];
Eat your heart out, Ruby. This one-liner filters a record collection for artists whose name matches “Bon Iver”, and initializes an NSImage
from the album cover image data of the matching albums.
Is this a good idea? Probably not. (NSPredicate
is rad, and breaking complicated logic up is under-rated)
Is this insanely cool? You bet! This clever example has shown a possible direction for future Objective-C DSLs and meta-programming.
KVC Collection Operators are a must-know for anyone who wants to save a few extra lines of code and look cool in the process.
While scripting languages like Ruby boast considerably more flexibility in its one-liner capability, perhaps we should take a moment to celebrate the restraint built into Objective-C and Collection Operators. After all, Ruby is hella slow, amiright? </troll>