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

Posted

技术标签:

【中文标题】检查 NSDictionary 中的键 - 不使用布尔 NSNumber 作为值 @iOS8【英文标题】:Check for key in NSDictionary - not working with boolean NSNumber as value @iOS8 【发布时间】:2015-01-08 17:06:10 【问题描述】:

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

    键的值是一个用布尔值YES初始化的NSNumber对象 dict[@"testKey"] or [dict objectForKey:@"testKey"] 用于检查key是否存在 检查结果存储到 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 设备(不是模拟器)上运行并且选中键的值是一个用布尔值 YES 初始化的 NSNumber 对象之外。在这种情况下,key-exists-check FAILS - 意味着即使 key 存在,变量 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

对此我没有任何解释。是bug吗?

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

已解决:感谢 CRD!这是我的代码中的一个错误,而不是在 iOS 8 中 - 令人惊讶。 ;-) BOOL keyCheck = [dict objectForKey:@"key1"]; 是检查密钥是否在字典中可用的无效方法。就是这样!你生活,你学习,你编码,你学习。谢谢大家的意见!

【问题讨论】:

BOOL opt_3 = ([dict objectForKey:@"key1"] ? 1 : 0); 由于您不能将 nil 值放入字典中,因此如果该值存在,则此测试将始终为真。 BOOL opt_1BOOL opt_2 的赋值是假的,因为你正在分配一个指向BOOL 的指针。 你绝对应该在这里使用[... boolValue]... 感谢您的回复!然而,对我来说,这没有任何意义。还要考虑到这段代码一直在工作,除了 iOS 8 的这种特殊情况。在我看来,如果你可以写“if (dict[@"key1"])”,你应该也可以写“BOOL 结果= if (dict[@"key1"])" - 这是相同的检查(使用指针,我知道)。 @Ian MacDonald:我不知道,但调用 [... boolValue] - 如果对象不存在也不是很好。 Hot Licks:是的,我知道。只是想举一个工作的例子。最好的解决方案是 "BOOL opt_3 = ([dict objectForKey:@"key1"]!=nil)" 我猜。 如果对象不存在,BOOL 结果将为NO(将nil 评估为BOOL 的结果)。您可以通过先检查其是否存在然后仅在 boolValue 存在时检查它来更改此默认值。在所有其他情况下,结果将正确地是包含在 NSNumber 原始包装器中的值。如果不访问boolValue,您是在寻址对象而不是原始值。 【参考方案1】:

好的,代码中有一个错误,它不是错误(尽管有些人可能会认为这是一个语言错误,但这是另一个问题;-))

在 C 中,条件语句接受一个被测试为“不等于 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 中的指针也可以转换为整数,并且不同大小的整数类型可以相互转换。如果转换为更大的类型,则转换为隐式,否则为显式 - 但是编译器可能会在后一种情况下发出警告并编译代码,就好像转换是明确的。这就是 Xcode 对您的两个语句所做的:

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

产生警告,BOOL 类型小于对象引用,但可以编译。您可以输入明确的(BOOL) 强制转换以使警告静音,但这不会修复错误。

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

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

在 iOS8 上,该值为0x3303b900,截断为一个字节为0x00,即BOOLNO

这些值当然是“随机的”——如果 iOS8 显然可以工作,或者混合使用,您可能会得到相反的结果。事实上,在程序的不同部分进行相同的测试可能会产生不同的结果。这也解释了为什么代表1NSNumber 可能与代表YES 的结果不同。

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

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

它应该可以工作。

HTH

【讨论】:

非常感谢!因此,条件语句的处理方式与我将同一语句分配给布尔变量的方式不同。因此,这段代码运行了几年只是“好运”——直到最低有效字节为 0x00。巧合的是,现在发生在 iOS 8 设备上。所以一切都很好。已经在我的项目中修复了它。不幸的是,我不能投票给你 :( 还是谢谢你! 好吧,在没有看到比较转换后,我们最终到达了那里。很高兴它有帮助。等待您的下一个惊喜是像 != 这样的 C 运算符实际上返回一个整数而不是布尔值,所以像 a += b > c 这样的语句是正确的......

以上是关于检查 NSDictionary 中的键 - 不使用布尔 NSNumber 作为值 @iOS8的主要内容,如果未能解决你的问题,请参考以下文章

typedef 枚举类型作为 NSDictionary 的键?

打印 NSDictionary 的键而不是值

使用具有多个值的键创建 NSDictionary

复制没有空值的 NSDictionary?

在 Objective-C 中使用 JSON 数据的 NSJSONSerialization 读取 NSDictionary 集的键值

使用 objectForKey 返回错误类型的键