[英]-[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.