如何安全地测试一个方法是否可以通过 NSInvocation 调用

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

我已经使用 ObjC 运行时生成了 class 的方法和属性列表,以便稍后可以使用 NSInvocation 从桥中调用它们。

问题是对于那些运行时无法生成签名的方法,我遇到了错误。

例如在 SKFieldNode 的实例中为 direction 调用 属性 getter 抛出异常 NSInvalidArgumentException,我猜那是因为 vector_float3没有编码类型,它的类型是''(即没有字符类型)

这是测试我所描述内容的方法:

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]"

如何使用内省来判断一个方法是否可以通过这种方式安全调用?

我尝试测试 NSMethodSignature return 的参数数量,但是对于缺少编码类型的方法,该值是错误的,例如这两个方法将 return 2 , 计算目标和选择器,以便不考虑剩余参数。

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

我也注意到方向 属性 is not available in Swift

这让我觉得这是因为同样的原因。所以我不介意在自定义桥中放弃对这些方法的支持。

您可以使用 try-catch 语句来尝试 运行 一些代码:

@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) {

}

这是一项相当简单的检查,可确保您没有任何编码不正确的参数:

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