检查 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_1
和BOOL 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”的表达式,并且有多种类型 - 数字类型(包括 char
和 BOOL
)和指针类型 - 可以是与 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
,即BOOL
NO
。
这些值当然是“随机的”——如果 iOS8 显然可以工作,或者混合使用,您可能会得到相反的结果。事实上,在程序的不同部分进行相同的测试可能会产生不同的结果。这也解释了为什么代表1
的NSNumber
可能与代表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 的键?
在 Objective-C 中使用 JSON 数据的 NSJSONSerialization 读取 NSDictionary 集的键值