3

Context: I have several CGPathRef's in a custom class, derived from NSObject, named model. I'm looking for a way to return a specific CGPathRef, based one a string I generate at runtime.

Simplified Example, if I could use KVC:

#model.h
@property (nonatomic) CGMutablePathRef pathForwardTo1;
@property (nonatomic) CGMutablePathRef pathForwardTo2;
@property (nonatomic) CGMutablePathRef pathForwardTo3;
...


#someVC.m
-(void)animateFromOrigin:(int)origin toDestination:(int)destination{
    int difference = abs(origin - destination);
        for (int x =1; x<difference; x++) {
            NSString *pathName = [NSString stringWithFormat:@"pathForwardTo%d", x];
            id cgPathRefFromString = [self.model valueForKey:pathName];
            CGPathAddPath(animationPath, NULL, cgPathRefFromString);
        }
}

Question: How can I access non-KVC compliant properties (CGPathRef) with just their name represented as a string?

Emin Israfil iOS
  • 1,801
  • 1
  • 17
  • 26
  • You refer to them as "properties". Do they have accessor methods? – Hot Licks Feb 04 '14 at 21:57
  • Yes, hopefully my edit clarified that a little. Thanks – Emin Israfil iOS Feb 04 '14 at 22:06
  • Certainly you can use `performSelector` type ops to invoke the getter methods, but it's not clear how you get that result type back. You might have to use `methodForSelector` and then invoke the "raw" method implementation, something I've never attempted. – Hot Licks Feb 04 '14 at 22:16
  • 1
    Why are they non-KVC compliant? Declared `@properties` are KVC compliant by default. – Andrew Madsen Feb 04 '14 at 22:26
  • 1
    @AndrewMadsen - The return values are not objects or scalars. – Hot Licks Feb 04 '14 at 22:30
  • @HotLicks, duh. Nevermind me... – Andrew Madsen Feb 04 '14 at 22:38
  • (Actually, CGMutablePathRef *might* be returned as a scalar that would conform to `id` as returned by `performSelector` -- hard to guess.) – Hot Licks Feb 04 '14 at 22:41
  • So you have a number of properties, part of whose name is an index. Why not just store your CGPathRefs in an array and have a single accessor that passes the index as a parameter i.e. `-(CGMutablePathRef) pathForwardToIndex: (size_t) idx;` – JeremyP Feb 05 '14 at 00:51
  • If the source can be modified then it's a simple problem -- put the values in a custom object or perhaps an NSValue object. – Hot Licks Feb 05 '14 at 01:04

3 Answers3

2

You should be able to use NSInvocation for this. Something like:

// Assuming you really need to use a string at runtime. Otherwise, hardcode the selector using @selector()
SEL selector = NSSelectorFromString(@"pathForwardTo1");
NSMethodSignature *signature = [test methodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setSelector:selector];
[invocation setTarget:test];
[invocation invoke];

CGMutablePathRef result = NULL;
[invocation getReturnValue:&result];

You can also do it directly with the proper objc_msgSend variant, but NSInvocation is easier (though probably significantly slower).

EDIT: I put a simple little test program here.

Andrew Madsen
  • 21,309
  • 5
  • 56
  • 97
1

This was discussed at length on cocoa-dev. The short answer is: you can't directly.

But it often turns out that there are a lot of ways around that. In your specific example, you should be storing these in an array, and then you wouldn't even need the KVC call.

Again, for your specific example, another possible solution is to switch to UIBezierPath, which can be converted to and from CGPath as you need.

As a more generic solution, you could wrap these into an object that just holds the value for you, and KVC on that. This is basically how NSValue is often used for wrapping raw pointers.

The totally generic solution is that you can implement valueForUndefinedKey:, inspect the key, and return what you want. You could, for instance, stick the paths in an array and index into it based on the key given. Or you could stick them in a dictionary and look them up there (this is basically what CALayer does). And of course if you really needed it to be dynamic, you could introspect with the runtime and basically reimplement KVC… it would be very rare that this would be the right answer. Pretty much all of these "generic" solutions are overkill for most problems.

The short answer is that KVC does not always play well with low-level objects that aren't toll-free bridged.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
0

What I would do (and have sometimes done) is wrap the CGPathRef in a UIBezierPath exactly so that it is an object and has all the benefits accruing thereto, including KVC, ability to stick it in an NSArray or NSDictionary, and ARC memory management. That, after all, is exactly what a UIBezierPath is (more or less).

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • 1
    It should also be noted that Apple specifically [recommends](https://developer.apple.com/library/ios/documentation/2ddrawing/conceptual/drawingprintingios/GraphicsDrawingOverview/GraphicsDrawingOverview.html#//apple_ref/doc/uid/TP40010156-CH14-SW20) using UIBezierPath to *create* paths in favor of CGPath unless you really need certain CoreGraphics features that UIBezierPath doesn't expose. – Andrew Madsen Feb 04 '14 at 23:28