Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Defining your own key path operators in cocoa

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

like image 530
cfischer Avatar asked Nov 04 '10 19:11

cfischer


People also ask

What is key-value coding in Cocoa?

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.

How are keys and key paths represented in Objective-C?

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.

How do you use key path based method calls?

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.

Why do we need key path expressions in KVO?

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.


1 Answers

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.

like image 81
Dave DeLong Avatar answered Oct 19 '22 04:10

Dave DeLong