NSUserDefaults中存储的NSNumber

6

刚才发生了一些奇怪的事情。

我在NSUserDefaults中存储了一个NSNumber,其值为unsigned long long。当我检索它时,值就改变了。系统似乎认为这个数字是long long而不是unsigned long long。

更糟糕的是,当我将从UserDefaults检索到的数字与原始数字进行比较时,结果是不相等

代码有什么问题吗?谢谢!

static NSString * const NumberKey = @"MyNumber";
unsigned long long value = 15908045869032883218ULL;
if ([[NSUserDefaults standardUserDefaults] objectForKey:NumberKey] == nil) {

    NSNumber *number = [NSNumber numberWithUnsignedLongLong:value];
    [[NSUserDefaults standardUserDefaults] setObject:number forKey:NumberKey];
    NSLog(@"Original Number:%@", number); // 15908045869032883218, right
}

NSNumber *number = [[NSUserDefaults standardUserDefaults] objectForKey:NumberKey];
NSLog(@"Current Number:%@", number); // -2538698204676668398, weird
NSLog(@"Current Value:%llu", [number unsignedLongLongValue]); // 15908045869032883218, right
NSLog(@"%d", [number isEqualToNumber:[NSNumber numberWithUnsignedLongLong:value]]); // 0
NSLog(@"%d", [number unsignedLongLongValue] == value); // 1
4个回答

6
进一步回答你的问题。如果你查看NSNumber文档中isEqualToNumber:函数,您会注意到以下行:

如果两个NSNumber对象具有相同的id值或等效的值,则它们被视为相等

重要的是您理解这一点。在您的代码中,您正在询问我的NSNumber对象“number”是否等于“value”,而不是问存储在我的NSNumber对象“number”中的数值是否等于存储在我的NSNumber对象“value”中的数值。

你写的最后一行代码表明,实际上你NSNumber的数字值是相等的。

NSLog(@"%d", [number unsignedLongLongValue] == value); //1

为了正确地存储和检索值,您应该使用NSNumber对象存储数字值(例如intValue == intValue,unsignedLongLongValue == unsignedLongLongValue)的==比较方法,而不是将它们的对象ID进行比较。
至于这行代码:
NSLog(@"Current Number:%@", number); // -2538698204676668398, weird

这并不奇怪,这是完全正常的。因为你已经告诉NSLog打印出一个'number'对象的NSObject表示形式。我不确定百分之百,但我相信NSNumber的- (NSString *)description函数默认返回它所包含的数值的无符号整型值。这就是为什么会返回一个大的负数。你可以尝试使用NSNumber的- (NSString *)descriptionWithLocale:(id)aLocale函数以更合理的方式打印数据,或者你可以使用...
NSLog(@"Current Number:%llu", [number unsignedLongLongValue]);

这将为您提供正确的答案。

编辑:

进一步研究后发现,从UserDefaults中回收NSNumber对象时,其原始数字类型未被保留(在NSNumber概述部分中强调了此信息)

(请注意,数字对象不一定保留它们创建时的类型。)

如果从用户默认设置中检索“number”后记录以下内容,则可以自行查看此内容(将其添加到您在问题中拥有的代码末尾),并查看此处显示的编码值。

NSLog(@"%s", [number objCType]); //This will log out q
NSLog(@"%s", [[NSNumber numberWithUnsignedLongLong:value] objCType]); //this will log out Q

Q和q的区别在于Q是无符号值,因此您在使用isEqualToNumber:函数时会遇到问题,因为数字类型不同。如果您一定要使用iSEqualToNumber:函数来比较值,那么可以实现此功能从NSUserDefaults中检索您的值。
NSNumber *number = [NSNumber numberWithUnsignedLongLong:[[NSUserDefaults standardUserDefaults] objectForKey:NumberKey] unsignedLongLongValue]];

你可以尝试使用NSNumber的compare:函数来判断返回值是否为NSOrderedSame,但是这种方法无法比较相同类型的有符号和无符号值。在你的情况下,我建议使用以上方法,因为从NSUserDefaults中检索数据会剥离数字的"signedness"。

我同意你的观点。然而,“如果两个NSNumber对象具有相同的id值或等效值,则认为它们相等”这句话意味着我可以依赖isEqualToNumber:方法来确定两个NSNumber对象的值,但我的代码结果表明我不能依赖这个方法。我必须自己决定值类型,并使用“XXValue”方法和“==”来比较两个NSNumber对象,这不太方便,因为isEqual:方法在许多其他对象中也被使用,如NSArray、NSDictionary等。 - Swordsfrog
@Swordsfrog 经过进一步的调查,您的问题的性质也与下面的一个答案有关。我也会编辑我的原始答案来证明这一点。 - Jamie Stewart

3

最终,如果你想将NSNumber存储到NSUserDefaults中,以下代码适用于我,即使是像881217446193276338这样的大整数。

保存:

[[NSUserDefaults standardUserDefaults] setObject:self.myUser.sessionid forKey:@"sessionid"];
[[NSUserDefaults standardUserDefaults] synchronize];

恢复数据的方法:

self.myUser.sessionid = (NSNumber *)[[NSUserDefaults standardUserDefaults] objectForKey:@"sessionid"];

2
我很高兴写下了这个答案,因为6个月后我又遇到了同样的问题,我谷歌搜索后来到了这里,看到了我的答案,再次解决了我的问题 :) - Ali

1

它正确地存储了,你的代码没有问题,除了:

NSLog(@"Current Number:%@", number);

这里的number是一个非字符串对象,你可以将其视为数值原语的包装器。或者你可以认为NSNumber实例将原始类型物化为对象。

你需要的是像这样的东西:

NSLog(@"Current Number:%@", [number stringValue]);


我想知道的是,为什么从NSUserDefaults检索到的数字与原始数字不相等。 - Swordsfrog

0

这里是一个推测性的答案:

NSNumber文档指出:

(请注意,数字对象不一定保留它们创建时的类型。)。

因此,它必须使用不同的内部存储来处理此类型,并且只有在您明确要求无符号长整型值时才会给出正确的值。在您的NSLog语句中调用的description方法可能默认为不同的表示类型。

并且/或者可能存在某些解档器的怪癖,防止isEqualToNumber方法对从默认返回的值起作用。如果您在同一范围内创建的两个NSNumbers之间进行比较,是否有效?鉴于您的最后一条语句返回true,正确的值肯定在其中某个地方。


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