如何安全地测试一个方法是否可以通过 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);
}
我已经使用 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);
}