【问题标题】:How to safely test whether a method can be called through NSInvocation如何安全地测试一个方法是否可以通过 NSInvocation 被调用
【发布时间】:2015-05-31 06:45:51
【问题描述】:

我已经使用 ObjC 运行时生成了一个类的方法和属性列表,以便以后可以使用 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:]: 索引 (1) 超出范围 [-1, 0]"

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

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

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

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

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

【问题讨论】:

标签: swift cocoa objective-c-runtime scripting-bridge


【解决方案1】:

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

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

【讨论】:

  • NSStringFromSelector(selector).UTF8String 是否可以解决 unicode 选择器名称的问题?不是我想使用一个,只是出于好奇
  • 谢谢,你的回答很聪明,我最终使用了selectorName = NSStringFromSelector(selector)numberOfArgs = 1 + [selectorName componentsSeparatedByString:@":"].count
【解决方案2】:

您可以使用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) {

}

【讨论】:

  • 我更喜欢自省,这样我就可以从列表中排除这些方法并使用 NSInvocation 安全地调用其余方法
  • 您可以将 try/catch 放入方法中作为检查器。不过,如果你知道更好的方法,请告诉我:)
  • 尝试 catch 不是一个好的解决方案,因为 ARC 不能很好地使用它,除非你打开那个编译器标志,这会极大地增加你的二进制文件大小。
  • @RichardJ.RossIII 谢谢!!
猜你喜欢
  • 2014-02-11
  • 1970-01-01
  • 2017-08-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多