0

I've generated a list of methods and properties of a class using the ObjC runtime, so that those can be called later from a bridge using NSInvocation.

The problem is that for those methods that the runtime can't generate a signature I'm getting an error.

For instance calling the property getter for direction in an instance of SKFieldNode throw the exception NSInvalidArgumentException, I’m guessing that’s because vector_float3 has no encoded type, its type is '' (i.e. no character type)

This is how to test what I'm describing:

Method method = class_getInstanceMethod([SKFieldNode class], @selector(direction));
const char *types = method_getTypeEncoding(method);
NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:types];
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:sig];

SKFieldNode *field = [SKFieldNode node];
field.direction = (vector_float3){1,2,3};

[inv setTarget:field];
[inv setSelector:@selector(direction)]; // (*)
[inv invoke];

vector_float3 v;

[inv getReturnValue:&v];

NSLog(@"%f %f %f", v.x, v.y, v.z);

(*) "NSInvalidArgumentException", "-[NSInvocation setArgument:atIndex:]: index (1) out of bounds [-1, 0]"

How can I tell, using introspection, whether a method can be safely called in that way?

I tried testing the number of arguments that NSMethodSignature returns, but the value is wrong for a method with missing encoded types, for instance this two methods will return 2, counting the target and selector, so that the remaining arguments are not taken into account.

- setDirection:(vector_float3)d1 direction:(vector_float3)d2;
- setDirection:(vector_float3)d;

I’ve also noticed that the direction property is not available in Swift

That make me think it’s because of this very same reason. So I wouldn't mind to drop support for those methods either in the custom bridge.

rraallvv
  • 2,875
  • 6
  • 30
  • 67

2 Answers2

1

This is a fairly simple check to make sure you don't have any improperly encoded arguments:

BOOL isMethodSignatureValidForSelector(NSMethodSignature *signature, SEL selector) {
    // This could break if you use a unicode selector name, so please don't do that :)
    const char *c_str = sel_getName(selector);
    unsigned numberOfArgs = 2;

    while (*c_str) {
        if (*c_str == ':') {
            numberOfArgs++;
        };

        c_str++;
    }

    return ([signature numberOfArguments] == numberOfArgs);
}
Richard J. Ross III
  • 55,009
  • 24
  • 135
  • 201
  • Does `NSStringFromSelector(selector).UTF8String` workaround the unicode selector name thing?, not that I want to use one, just out of curiosity – rraallvv Jun 01 '15 at 15:40
  • 1
    Thanks, your answer was very clever, I ended up using `selectorName = NSStringFromSelector(selector)` and `numberOfArgs = 1 + [selectorName componentsSeparatedByString:@":"].count` – rraallvv Jun 01 '15 at 16:01
0

You can use a try-catch statement to try and run some code:

@try {
    Method method = class_getInstanceMethod([SKFieldNode class], @selector(direction));
    const char *types = method_getTypeEncoding(method);
    NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:types];
    NSInvocation *inv = [NSInvocation invocationWithMethodSignature:sig];

    SKFieldNode *field = [SKFieldNode node];
    field.direction = (vector_float3){1,2,3};

    [inv setTarget:field];
    [inv setSelector:@selector(direction)]; // (*)
    [inv invoke];

    vector_float3 v;

    [inv getReturnValue:&v];

    NSLog(@"%f %f %f", v.x, v.y, v.z);
} @catch (NSException *exception) {

}
Bannings
  • 10,376
  • 7
  • 44
  • 54