Is it possible to define your own key path operators, such as @avg, @sum, etc…
Key-value coding is a fundamental concept that underlies many other Cocoa technologies, such as key-value observing, Cocoa bindings, Core Data, and AppleScript-ability. Key-value coding can also help to simplify your code in some cases.
In Objective-C, keys and key paths are represented by strings. And remember that KVO is only possible because Swift uses the Objective-C runtime. Notice that the key path used in addObserver (_:forKeyPath:options:context:) is relative to the current scope and context.
When you have a hierarchy of key-value coding compliant objects, you can use key path based method calls to drill down, getting or setting a value deep within the hierarchy using a single call.
Thanks to the addition of key path expressions, the compiler can check the validity of the key path at compile time. String literals don't have this advantage, which often lead to bugs in the past. If the key path is deemed valid by the compiler, it is replaced by a string literal at compile time. Why? KVO uses the Objective-C runtime.
Short answer: Kinda. You can override valueForKeyPath:
to intercept your custom operator or forward on to super
, but that can be problematic (I'll leave the explanation to that as an exercise to the reader).
Long answer: Yes you can, but it relies on using private behavior (not private api).
After some neat introspection of NSArray
, I found some private methods:
_distinctUnionOfSetsForKeyPath:
_distinctUnionOfObjectsForKeyPath:
_distinctUnionOfArraysForKeyPath:
_unionOfSetsForKeyPath:
_unionOfArraysForKeyPath:
_unionOfObjectsForKeyPath:
_minForKeyPath:
_maxForKeyPath:
_countForKeyPath:
_avgForKeyPath:
_sumForKeyPath:
Well, neat! Those methods seem to match the operators you can use with collections: @sum
, @min
, @max
, @distinctUnionOfObjects
, etc. The @
has been replaced with an underscore and we've got ForKeyPath:
appended.
So it would seem that we can create a new method to match the appropriate signature and we're good to go.
So:
@interface NSArray (CustomOperator)
- (id) _fooForKeyPath:(NSString *)keyPath;
@end
@implementation NSArray (CustomOperator)
- (id) _fooForKeyPath:(NSString *)keyPath {
//keyPath will be what comes after the keyPath. In this example, it will be "self"
return @"Hello world!";
}
@end
NSArray * array = [NSArray arrayWithObjects:@"1", @"2", @"3", nil];
NSLog(@"%@", [array valueForKeyPath:@"@foo.SELF"]); //logs "Hello world!"
It works, but I'm not sure I would rely on this, since it relies on an implementation detail that could change in the future.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With