[英]-[NSInvocation getReturnValue:] with double value produces 0 unexpectedly
我正在尝试调用一个使用NSInvocation
返回double
NSInvocation
。 但我发现它在 64 位 iOS 应用程序中不起作用。 它适用于 OS X 模拟器——32 位和 64 位——iPad 2 和 32 位版本的 iPad Air。 只有 iPad Air 设备上的 64 位版本有这个问题。
这是演示问题的代码:
NSMethodSignature *signature = [NSString instanceMethodSignatureForSelector:@selector(doubleValue)];
for (int i = 0; i < 10; i++) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
NSString *str = [NSString stringWithFormat:@"%d", i];
[invocation setTarget:str];
[invocation setSelector:@selector(doubleValue)];
[invocation invoke];
union {
double d;
uint64_t u;
} d;
[invocation getReturnValue:&d.d];
NSLog(@"%lf, %llx", d.d, d.u);
}
预期产出
2013-11-09 22:34:18.645 test[49075:907] 0.000000, 0
2013-11-09 22:34:18.647 test[49075:907] 1.000000, 3ff0000000000000
2013-11-09 22:34:18.648 test[49075:907] 2.000000, 4000000000000000
2013-11-09 22:34:18.649 test[49075:907] 3.000000, 4008000000000000
2013-11-09 22:34:18.650 test[49075:907] 4.000000, 4010000000000000
2013-11-09 22:34:18.651 test[49075:907] 5.000000, 4014000000000000
2013-11-09 22:34:18.652 test[49075:907] 6.000000, 4018000000000000
2013-11-09 22:34:18.653 test[49075:907] 7.000000, 401c000000000000
2013-11-09 22:34:18.654 test[49075:907] 8.000000, 4020000000000000
2013-11-09 22:34:18.654 test[49075:907] 9.000000, 4022000000000000
iPad Air 上 64 位版本的输出
2013-11-09 22:33:55.846 test[998:60b] 0.000000, 18710a969
2013-11-09 22:33:55.847 test[998:60b] 0.000000, 18710a969
2013-11-09 22:33:55.848 test[998:60b] 0.000000, 18710a969
2013-11-09 22:33:55.848 test[998:60b] 0.000000, 18710a969
2013-11-09 22:33:55.849 test[998:60b] 0.000000, 18710a969
2013-11-09 22:33:55.849 test[998:60b] 0.000000, 18710a969
2013-11-09 22:33:55.850 test[998:60b] 0.000000, 18710a969
2013-11-09 22:33:55.850 test[998:60b] 0.000000, 18710a969
2013-11-09 22:33:55.851 test[998:60b] 0.000000, 18710a969
2013-11-09 22:33:55.851 test[998:60b] 0.000000, 18710a969
这也发生在float
值上。
2013-11-09 23:51:10.021 test[1074:60b] -0.000000, 80000000
2013-11-09 23:51:10.023 test[1074:60b] -0.000000, 80000000
2013-11-09 23:51:10.024 test[1074:60b] -0.000000, 80000000
2013-11-09 23:51:10.024 test[1074:60b] -0.000000, 80000000
2013-11-09 23:51:10.025 test[1074:60b] -0.000000, 80000000
2013-11-09 23:51:10.026 test[1074:60b] -0.000000, 80000000
2013-11-09 23:51:10.026 test[1074:60b] -0.000000, 80000000
2013-11-09 23:51:10.027 test[1074:60b] -0.000000, 80000000
2013-11-09 23:51:10.027 test[1074:60b] -0.000000, 80000000
2013-11-09 23:51:10.028 test[1074:60b] -0.000000, 80000000
同意@David H 在这种情况下NSInvocation
被破坏,或者可能是NSString
doubleValue
方法。 然而,我能够强迫它工作。
在我看来,由于调用约定问题/不匹配, NSInvocation
被破坏了。 通常,objective-c 方法的参数和返回值在寄存器中传递。 ( objc_msgSend
知道如何执行这种类型的调用。)但是如果参数或返回值是一个不适合寄存器的结构或类型,那么它们将被传递到堆栈上。 ( objc_msgSend_stret
执行这种类型的调用。) NSInvocation
通常使用方法签名来决定是否需要调用objc_msgSend
或objc_msgSendStret
。 我猜现在它还需要知道它在什么平台上运行,这就是错误所在。
我玩了一下你的代码,似乎在 arm64 上,双返回值是作为结构传递的,但NSInvocation
将其视为在寄存器中传递。 我不知道哪一边是正确的。 (我只知道在这方面很危险。我的手指交叉,比我低级的人走过来读到这个,并提供了更好的解释!)
也就是说,在我看来,在 arm(32 位)和 arm64 中传递参数和结果的方式发生了重大变化。 请参阅arm64 的ARM 过程调用标准和ARM 过程调用标准(非 64 位)中的结果返回部分。
我能够强制NSInvocation
将调用视为返回包含双NSInvocation
的结构,这使其按预期工作。 为此,我将方法签名伪造为返回结构的方法的伪造签名。 我把它放在一个NSString
类别中,但它可以存在于任何地方。
不知道具体是什么坏了,或者修复后会发生什么,我不太可能用这个“修复”来发布代码。 我会找到一些其他的解决方法。
typedef struct
{
double d;
} doubleStruct;
@interface NSString (TS)
- (doubleStruct) ts_doubleStructValue;
@end
@implementation NSString (TS)
- (doubleStruct) ts_doubleStructValue
{
doubleStruct ds;
return ds;
}
@end
- (void) test
{
NSMethodSignature *signature = [NSString instanceMethodSignatureForSelector: @selector( ts_doubleStructValue )];
for (int i = 0; i < 10; i++) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
NSString *str = [NSString stringWithFormat:@"%d", i];
[invocation setTarget:str];
[invocation setSelector:@selector(doubleValue)];
[invocation invoke];
double d;
[invocation getReturnValue: &d];
NSLog(@"%lf", d);
}
}
您目前没有希望让它发挥作用。 我尝试了您代码的许多变体,并在 iPhone 5S 和最新的模拟器上以 32 位和 64 位模式进行了测试,但这一定是 arm64 的错误,因为 64 位模拟器工作得很好。
所以首先我修改了你的代码以尝试各种变化,结果使用floatValue
失败了。 由于浮点数的大小很常见,这减少了各种试验平台之间的变量数量。
此外,我尝试使用使用积分和浮点方法创建的 NSNumber 目标:浮点方法实际上会导致崩溃! 我尝试了其他选项,如保留字符串和设置调用保留设置,没有改变。
我已经输入了一个错误: 15441447: NSInvocation fails only on arm64 devices
- 任何关心它的人都可以15441447: NSInvocation fails only on arm64 devices
它。 我也上传了一个演示项目。
我上传的代码是:
- (void)test
{
NSMethodSignature *signature = [NSString instanceMethodSignatureForSelector:@selector(floatValue)];
for (int i = 0; i < 10; i++) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
#if 0
NSString *str = [NSString stringWithFormat:@"%d", i];
[invocation setTarget:str]; // fails on iPhone 5s
#else
//[invocation setTarget:[NSNumber numberWithFloat:(float)i]]; // crashes in 'invoke' on iPhone 5s, works fine in simulator
[invocation setTarget:[NSNumber numberWithInteger:i]]; // fails on iphone 5S
#endif
[invocation setSelector:@selector(floatValue)];
[invocation invoke];
float f;
[invocation getReturnValue:&f];
NSLog(@"%f", f);
}
}
根据@TomSwift 答案进行修复。
- (void)testInvocation
{
NSInvocation *invocation = [[self class] invocationWithObject:self selector:@selector(getAFloat)];
[invocation setTarget:self];
[invocation invoke];
double d;
[invocation getReturnValue: &d];
NSLog(@"d == %f", d);
return YES;
}
+ (NSInvocation *)invocationWithObject:(id)object selector:(SEL)selector
{
NSMethodSignature *sig = [object methodSignatureForSelector:selector];
if (!sig) {
return nil;
}
#ifdef __LP64__
BOOL isReturnDouble = (strcmp([sig methodReturnType], "d") == 0);
BOOL isReturnFloat = (strcmp([sig methodReturnType], "f") == 0);
if (isReturnDouble || isReturnFloat) {
typedef struct {double d;} doubleStruct;
typedef struct {float f;} floatStruct;
NSMutableString *types = [NSMutableString stringWithFormat:@"%s@:", isReturnDouble ? @encode(doubleStruct) : @encode(floatStruct)];
for (int i = 2; i < sig.numberOfArguments; i++) {
const char *argType = [sig getArgumentTypeAtIndex:i];
[types appendFormat:@"%s", argType];
}
sig = [NSMethodSignature signatureWithObjCTypes:[types UTF8String]];
}
#endif
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:sig];
[inv setSelector:selector];
return inv;
}
哇,这在今天和现在在模拟器中仍然明显损坏(我正在测试它)。 今天我被这个错误所困扰,发现我的双参数总是在调用的选择器中返回零。
在我的情况下,很容易将它作为 NSNumber 发送,@(myDouble) 作为参数,然后在另一边用 myNumber.doubleValue 解开它
讨厌。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.