KVC and Collection Operators
KVC, known as Key-Value Coding, is a coding style where properties and methods of an object are accessible via string identifiers. For much comprehensive detail and definition for key-value coding, read the apple docs.
In NSFoundation framework, any NSObject subclass automatically conforms NSKeyValueCoding
protocol.
To derive a better understanding of KVC, I personally prefer to dive in code examples.
Simple KVC
KVC basis includes two essential methods valueForKey:
and setValue:ForKey:
Person *person = [Person new];
person.name = @"John Smith";
[person valueForKey:@"name"]; // John Smith
[person setValue:@20 forKey:@"age"];
[person valueForKey:@"age"]; // 20
The above example works just because the class Person
has accessor methods/properties named name
and age
. If we were asking for the value of key fullname
which is not defined in Person
interface, we would get this error.
[person valueForKey:@"fullname"];
//'NSUnknownKeyException', reason: '[<Person 0x7ffdd9d183e0> valueForUndefinedKey:]:
//this class is not key value coding-compliant for the key fullname.
Another method, valueForKeyPath:
allows to access nested properties on the receiver object:
[object valueForKey:@"property.anotherProperty.yetAnother"];
Showing in our person example:
Person *child = [Person new];
child.name = @"Jane Doe";
person.child = child;
[person valueForKeyPath:@"child.name.uppercaseString"]; // JANE DOE
Notice that key-value coding includes not only properties but also no-parameter methods. uppercaseString
is not a property of NSString
but also accessible via KVC.
KVC methods on collections results a new array which consists of objects pointed with the keyPath
.
If we had an employees array for instance, we could call
[employees valueForKeyPath:@"phone.model"]; // [@"Apple",@"Samsung", @"HTC",@"Blackberry"]
and get another array including phone models of employees.
Collection Operators
According to the documentation;
Collection operators allow actions to be performed on the items of a collection using key path notation and an action operator.
Predefined collection operators are:
- @avg
- @count
- @max
- @min
- @sum
And notation for collection operators are shown in the docs:
Let’s express them with some examples, consider a our person object:
@interface Person : NSObject
@property (nonatomic,strong) NSString *name;
@property (nonatomic,strong) NSNumber *age;
@property (nonatomic) int height;
@property (nonatomic,strong) Person *child;
@end
and a random people array:
Name | Age | Height |
---|---|---|
John | 30 | 180 cm |
Kelly | 15 | 176 cm |
Simon | 24 | 196 cm |
Chris | 3 | 100 cm |
Person *john = ...
Person *kelly = ...
Person *simon = ...
Person *chris = ...
NSArray *people = [john,kelly,simon,chris];
[people valueForKeyPath:@"@avg.age"]; // 18
[people valueForKeyPath:@"@avg.height"]; // 163
[people valueForKeyPath:@"@max.age"]; // 30
[people valueForKeyPath:@"@min.height"]; // 100
[people valueForKeyPath:@"@sum.age"]; // 72
[people valueForKeyPath:@"@sum.height"]; // 652
..
..
The first line with a collection operator is [people valueForKeyPath:@"@avg.age"]
. Here the path @avg.age
is a virtual path, calculating the average value of the property age
.
Here is the secret behind the scenes:
@avg
virtual path first calls[people valueForKeyPath:@"age"]
- That call results in a new collection of NSNumbers,
[@30,@15,@24,@3]
in our example - After that, generates the average value of that array.
Similarly we can average the height
out by calling the key path @avg.height
.
@max
, @min
and @sum
is self-explanatory, they are all used to make calculations a property of and item in the whole collection. @min
for instance, we could also write the same expression as follows:
NSNumber *max = @0;
for (Person *person in people) {
if (person.age.integerValue > max.integerValue) {
max = person.age;
}
}
// max = 30
Collection operators are not only reducing our code, also adds more readability to it.
Object Operators
Like the collection operators, object operators applied to a single collection instance.
- @distinctUnionOfObjects
- @unionOfObjects
@distinctUnionOfObjects
eliminates the duplicated items according to the specified key path. Let’s assume we have an array with duplicated items:
NSArray *items = @[@"iPhone 6s",@"Samsung",@"Samsung Edge",@"iphone",@"iPhone 6s",@"Samsung"];
[items valueForKeyPath:@"@distinctUnionOfObjects.self"]; // iPhone 6s,@"Samsung",@"Samsung Edge",@"iphone"
Notice that self
after the operators cause the operators run on the object itself.
Explained in the docs, the leaf object (here is self ) can not be nil for
@distinctUnionOfObjects
operator.
@unionOfObjects
basically does the same as we call
[items valueForKeyPath:@"self"];
therefor duplicated are not removed, and no special behavior also.
Array and Set Operators
There is also another bunch of operators, @distinctUnionOfArrays, @unionOfArrays, @distinctUnionOfSets
, which operates on arrays or arrays. Sample code using the people array above, explains better:
NSArray *workers = @[john,kelly,simon,chris];
NSArray *friends = @[john,alice,chris];
[@[workers,friends] valueForKeyPath:@"@distinctUnionOfArrays.name"];
// [@"John",@"Kelly",@"Simon",@"Chris",@"Alice"]
[@[workers,friends] valueForKeyPath:@"@unionOfArrays.name"];
// [@"John",@"Kelly",@"Simon",@"Chris",@"John",@"Alice",@"Chris"]
@distinctUnionOfSets
operates exactly the same as @distinctUnionOfObjects
, except they are used on NSSet
instances.
Bonus: Custom Operators
Despite there is no documentation from apple on customizing operators, Nicolas Bachschmidt showed a way to implements custom operators.
Let’s create a @multiply
operator, which multiplies every item specified with the keypath, returns the result.
In order to create a custom operator, we must know what the operators do behind the scene.
For instance @avg
operator, calls _avgForKeyPath:
on its receiver. Same for @min
, it calls _minForkeyPath:
on its receiver which is an NSArray instance.
Therefore if we would have a method called _multiplyForKeyPath:
, then we can use @multiply
operator for an array of NSNumber
instances.
@interface NSArray (Multiply)
- (NSNumber *) _multiplyForKeyPath:(NSString *)keyPath;
@end
@implementation NSArray (Multiply)
-(NSNumber *)_multiplyForKeyPath:(NSString *)keyPath
{
NSArray *values = [self valueForKeyPath:keyPath];
float result = 1.0f;
for (NSNumber *number in values) {
result = result * number.floatValue;
}
return @(result);
}
Finally, the usage of our new @multiply
operator:
[@[@3,@2.2,@1] valueForKeyPath:@"@multiply.self"] // 6.6
You can extract as many operators as you want, depends on your imagination. An example of an advanced operator is
[people valueForKeyPath: @"[distinct].{age<10}.name"];
which is impossible with the current implementation, but kicks and idea what custom operators look like in the future.
Honestly I doubt this code is totally safe for AppStore App validation, but for the next version in iOS, hopefully we will find much broad implementation on operators.