Search code examples
swiftcocoaobjective-c-runtimescripting-bridge

How to safely test whether a method can be called through NSInvocation


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.


Solution

  • 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);
    }