15

Is it possible to define your own key path operators, such as @avg, @sum, etc…

cfischer
  • 24,452
  • 37
  • 131
  • 214
  • 2
    I have to keep fighting the urge to send `cocoa` questions over to the cooking site... – Mark Ransom Nov 04 '10 at 20:17
  • 2
    +1 this is a really fascinating question; one that I've never thought to ask, but one that has revealed some really interesting information. Thanks for asking! – Dave DeLong Nov 04 '10 at 21:13

2 Answers2

14

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.

Dave DeLong
  • 242,470
  • 58
  • 448
  • 498
  • 2
    Override `-valueForKey:` instead though I would. Read the docs on how NSDictionary implements it. – Mike Abdullah Nov 04 '10 at 21:53
  • 1
    @Mike in the long run it's probably stabler, but the problem with overriding is that these operators are usually only useful on collections, and subclassing collections kinda sucks. :( – Dave DeLong Nov 04 '10 at 21:56
  • 1
    Nice find Dave! However, it does seem a bi brittle as you said. Why do you think that overriding valueForKeyPath: is more dangerous? – cfischer Nov 04 '10 at 22:00
  • 1
    @Fernando: subclassing collections is tricky compared to a category and I haven't played around with it to know what would be involved. Are there specific edge cases you'd have to code for? I don't know. The category approach, though seemingly brittle, just seems simpler to me. – Dave DeLong Nov 04 '10 at 22:10
  • You could also make use of Matt Gallagher's ["Supersequent Implementation"](http://cocoawithlove.com/2008/03/supersequent-implementation.html) technique for categories to basically allow you to just categorize `-valueForKey:` on `NSArray`. Catch your own custom key path operators, and send anything else back up to the "supersequent" implementation. – CIFilter Feb 14 '11 at 05:28
0

It's possible by overriding valueForKeyPath: and doing your own custom logic in there, but there's no framework support for it.

Chuck
  • 234,037
  • 30
  • 302
  • 389