检查 NSDictionary 中的键 - 不使用布尔 NSNumber 作为值 @iOS8

Check for key in NSDictionary - not working with boolean NSNumber as value @iOS8

我刚刚在 iOS 8 (@ 8.1.1 & 8.1.2) 上发现了 NSDictionary 的一个奇怪问题。我可以测试的所有其他设备(包括 (8.1) 模拟器)都没有遇到这个问题。
使用 iOS8 检查 NSDictionary 对象中的键在以下条件下无法按预期工作:

  1. 键的值是一个用布尔值 YES 初始化的 NSNumber 对象
  2. dict[@"testKey"] 或 [dict objectForKey:@"testKey"] 用于检查key是否存在
  3. 校验结果存入BOOL变量

先看下面的示例代码(我知道你可以将整个检查机制压缩在一行中 - 这只是为了说明问题):

-(void) doSomethingDependingOn:(BOOL)foo andBar:(BOOL)bar andAttr:(NSDictionary*)dict {
BOOL doAction = NO;

    if (foo) {
        doAction = dict[@"trigger1"];   // check if key exists in dictionary
    }

    if (bar) {
        doAction = dict[@"trigger2"];   // check if key exists in dictionary
    }

    if (doAction) {
        // do something
    }
}

此代码运行良好 - 除了它在真正的 iOS 8 设备(而非模拟器)上运行的情况外,并且选中键的值是一个 NSNumber 对象,该对象用布尔值 YES 初始化。在这种情况下,key-exists-check FAILS——这意味着变量 doAction 被设置为 NO,尽管该 key 存在并且您找到了该键的完美对象(布尔值 YES 或整数 1 的 NSNumber)。
对我来说,这似乎是一个 iOS 8 错误。

您可以使用以下代码验证这一点:

NSDictionary *dict = @{@"key1" : [NSNumber numberWithBool:YES], @"key2" : @"value2"};
BOOL opt_1 = [dict objectForKey:@"key1"];
BOOL opt_2 = dict[@"key1"];
BOOL opt_3 = ([dict objectForKey:@"key1"] ? 1 : 0);

if ([dict objectForKey:@"key1"]) NSLog(@"1. OK");
if (dict[@"key1"]) NSLog(@"2. OK");

if (opt_1) NSLog(@"opt_1 OK");
if (opt_2) NSLog(@"opt_2 OK");
if (opt_3) NSLog(@"opt_3 OK");

NSLog(@"check ref: [NSNumber numberWithBool:YES] -> %p\tdict[@\"key1\"] -> %p",[NSNumber numberWithBool:YES],dict[@"key1"]);

输出@iOS8:

- 1. OK
- 2. OK
- opt_3 OK
- check ref: [NSNumber numberWithBool:YES] -> 0x3303b900    dict[@"key1"] -> 0x3303b900

输出@其他设备:

- 1. OK
- 2. OK
- opt_1 OK
- opt_2 OK
- opt_3 OK
- check ref: [NSNumber numberWithBool:YES] -> 0x3eb319f0    dict[@"key1"] -> 0x3eb319f0

我对此没有任何解释。这是一个错误吗?

编辑:为指向值的指针引用添加打印输出(值检查)

已解决:感谢 CRD!这是我代码中的错误,而不是 iOS 8 - 惊喜。 ;-) BOOL keyCheck = [dict objectForKey:@"key1"]; 是检查字典中键是否可用的无效方法。而已!你生活,你学习,你编码,你学习。感谢大家的参与!

好的,代码中有一个错误,它不是一个错误(虽然有些人可能会争辩说它是一个 语言 错误,但那是另一个问题;-))

在 C 中,条件语句接受一个表达式,该表达式被测试为 "unequal to 0",并且有多种类型 - 数字类型(包括 charBOOL)和指针类型 - 可以与 0 进行比较。对于指针 0 表示 空指针常量 ,通常在 Obj-C 中表示为 nil。所以你的声明:

BOOL opt_3 = ([dict objectForKey:@"key1"] ? 1 : 0);

根据语言规范直接等同于:

BOOL opt_3 = ([dict objectForKey:@"key1"] != nil ? 1 : 0);

同样在C中指针可以转换为整数,不同大小的整数类型可以相互转换。如果转换为更大的类型,则转换为 implicit,否则为 explicit - 但是编译器可能只是在后一种情况下发出警告并编译代码就像转换是显式的一样。这就是 Xcode 对你的两个语句所做的:

BOOL opt_1 = [dict objectForKey:@"key1"];
BOOL opt_2 = dict[@"key1"];

产生警告,BOOL 类型小于对象引用,但可以编译。您可以输入一个明确的 (BOOL) 转换来消除警告,但这不会修复错误。

问题来自 C 如何将较大类型转换为较小类型 - 它只是 截断 - 无论结果 mathematical/logical 正确性如何。所以上面两个语句所做的是将指针值的最低有效字节分配给 BOOL 变量(因为 BOOL 是一个字节)。

现在查看您添加到问题中的指针值。在非 iOS8 设备上,值为 0x3eb319f0,截断为一个字节的值为 0xf0,并且解释为 BOOL 的值为真(任何非零均为真) .

在iOS8上的值为0x3303b900,截断为一个字节为0x00,即BOOL NO.

这些值当然是 "random" - 如果 iOS8 显然有效,或者有些混合,您可能会得到相反的结果。事实上,在程序的不同部分进行相同的测试 可能 产生不同的结果。这也解释了为什么代表 1NSNumber 可能会给出与代表 YES.

不同的结果

要修复您的代码,只需将其更改为:

BOOL opt_1 = [dict objectForKey:@"key1"] != nil;
BOOL opt_2 = dict[@"key1"] != nil;

它应该可以工作。

HTH