为什么在32位系统上使用setValue:forKey:会失败,而在64位系统上却不会?

4

我最近向苹果提交了一个关于此问题的错误报告,但我想问一下是否有什么明显的遗漏。在Objective-C中,在64位系统上,以下调用可以正常工作,但在32位系统上会抛出一个NSInvalidArgumentException异常:

[self setValue:@"true" forKey:@"flag"];

“flag”属性是一个BOOL类型的属性:
@property BOOL flag;

此外,在Swift/32位中,该属性为Bool时调用正常:

var flag: Bool = false

同样的代码在64位系统上可以正常运行,但在32位系统上会抛出NSInvalidArgumentException异常("index"是一个Int类型):

setValue("2", forKey: "index")

然而,在Objective-C/32位中,这个属性是一个NSInteger,在那里它可以正常工作。

我本来期望这些调用会正确地工作,无论是哪种语言或处理器架构。有没有人能解释一下为什么它们可能不起作用呢?


1
但是在你发布的代码中,你将值作为 @"true"NSString 传递。那不是一个 BOOL。你不能将 NSString 传递给 BOOL 属性。这就是问题所在。 - rmaddy
3
不,它不起作用。如果起作用的话,它将适用于32位和64位系统。我所说的是正确的。你需要传递(至少对于Objective-C)一个BOOL值,而不是一个NSString。但是你不能传递一个BOOL,因为它不是一个对象类型。尝试传递等效的NSNumber值来表示YES,代码如下:[self setValue:@YES forKey:@"flag"]; - rmaddy
3
KVC 文档详细介绍了原始数值类型(包括 BOOL)如何使用 NSNumber 进行处理。如果你希望你的代码正常运行,请将你的真/假字符串转换成正确的 NSNumber 类型。 - rmaddy
2
请参见https://dev59.com/GlDTa4cB1Zd3GeqPIFKi。 - rmaddy
1
我不认为KVC可以透明地执行任何字符串到数字或字符串到布尔值的转换。如果这真的起作用了,那么只能说是纯属巧合。正如rmaddy所说,你必须传递一个NSNumber类型的变量。在Swift中,你还可以传递Bool或Int类型的变量,因为它们会被自动转换为NSNumber类型。 - Martin R
显示剩余14条评论
2个回答

5
如果您将所有评论结合起来,答案就在那里...
对于基本类型属性,setValue:forKey:不需要NSNumber/NSValue,但通常会传递一个。
观察到的问题也不是因为64位与32位之间的区别,示例代码在64位系统上也可能失败。
这完全取决于BOOL的性质以及它是否为charbool,正如一条评论所建议的那样 - 这取决于许多因素(从在10.10.5上运行的Xcode 6.4)。
/// Type to represent a boolean value.
#if !defined(OBJC_HIDE_64) && TARGET_OS_IPHONE && __LP64__
typedef bool BOOL;
#else
typedef signed char BOOL; 
// BOOL is explicitly signed so @encode(BOOL) == "c" rather than "C" 
// even if -funsigned-char is used.
#endif

setValue:forKey在设置<type>的基本类型属性时,调用- <type>Value方法来处理其所传递的对象。

如果BOOLchar类型,则调用- charValue方法;而NSString没有这样的方法,因此会失败。

如果BOOLbool类型,则调用- boolValue方法;而NSString具有��方法,因此一切正常。

简单修复:

@property bool flag;

这样做可以在任何地方使用,并且有额外的好处,即flag将始终为true/false、YES/NO、1/0,而不是char可能出现的其他254种可能性之一。

想象一下,如果C语言的设计者没有省略并从一开始就包含了真正的布尔类型,事情会有多么不同...


“setValue:forKey” 在设置调用的基本类型属性时,会在传递给它的任何对象上调用 <type>Value。这是从 KVC 文档中提取的吗? - Greg Brown
这似乎是正确的。根据苹果的文档:“...setValue:forKey: 确定适当访问器或实例变量所需的数据类型,用于指定的键。如果数据类型不是对象,则使用适当的 -<type>Value 方法从传递的对象中提取值。” - Greg Brown
这就完美地解释了为什么当BOOL被定义为char时会失败。但我仍然不确定为什么Swift Int也会失败。我需要再深入研究一下(除非有人已经知道答案并愿意分享)。 - Greg Brown
解决Swift问题的答案:在32位系统上,Swift的Int类型似乎是long(4字节),而在64位系统上是long long(8字节)。但NSString没有定义longValue方法,因此会失败。 - Greg Brown

1

正如@CRD所提到的,BOOL在32位设备上是char的typedef,而NSString没有charValue方法,因此我们可以添加一个简单的NSString类别来修复它,像这样。

#import "NSString+DCCharValue.h"

@implementation NSString (DCCharValue)

#if !defined(OBJC_HIDE_64) && TARGET_OS_IPHONE && __LP64__
#else
- (BOOL)charValue {
    return [self boolValue];
}
#endif

@end

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接