– 带有double值的意外地产生0

我想调用一个方法,返回一个使用NSInvocationdouble NSInvocation 。 但我发现它不适用于64位iOS应用程序。 它适用于OS X,模拟器(包括32位和64位),iPad 2和iPad Air(32位版本)。 只有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", dd, du); } 

预期产出

 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知道如何执行这种types的调用。)但是,如果参数或返回值是一个不适合寄存器的结构或types,那么它们将被传递到堆栈上。 ( objc_msgSend_stret执行这种types的调用。) NSInvocation通常使用方法签名来决定是否需要调用objc_msgSendobjc_msgSendStret 。 我猜测现在它也需要知道它在运行什么平台,而这正是错误所在。

我玩了一下你的代码,看起来在arm64上,双重返回值是作为一个结构传递的,而NSInvocation把它当作传入一个寄存器。 我不知道哪一方是正确的。 (我知道在这个领域只有足够的危险,我的手指越过,那些比我更低级别的人来读这个,并提供一个更好的解释!)

也就是说,在我看来,参数和结果在arm(32bit)和arm64中的传输方式有很大的变化。 请参阅arm64和ARM过程调用标准 (非64位)的ARM过程调用标准中的“ 结果返回”部分。

我能够强制NSInvocation将该调用视为返回一个包含双NSInvocation的结构,这使得它按预期工作。 为了做到这一点,我伪造了方法签名到返回结构的方法的假签名。 我把它放在一个NSString类别中,但它可以在任何地方生活。

不知道具体是什么被打破了,或者当它被修复时会发生什么,我不会用这个“修复”来发送代码。 我会find一些其他的解决方法。

 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位模式下testing,但这必须是一个与arm64的错误,因为64位模拟器工作得很好。

所以首先我修改你的代码来尝试各种变化,结果使用floatValue失败了。 由于浮动的大小是常见的,这减less了各个试验平台之间的variables的数量。

此外,我试图使用一个NSNumber目标创build使用积分和浮法的方法:浮法方法实际上导致崩溃! 我尝试其他选项,如保留string,并设置调用保留设置,没有改变。

我已经input了一个错误: 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; }