NSNumber stored in NSUserDefaults
Asked Answered
P

4

6

Something weird just happened.

I stored a NSNumber with an unsigned long long value in NSUserDefaults. When I retrieve it, the value just changed. It seems that system thinks the number is long long instead of unsigned long long.

What's worse is that when I compare the number retrieved from UserDefaults with the original number, the result is NotEqual!

what's wrong with the code? Thank you!

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
Package answered 6/10, 2011 at 5:32 Comment(0)
S
6

To further answer your question. If you look in the documentation for NSNumber's isEqualToNumber: function you will notice the following line,

Two NSNumber objects are considered equal if they have the same id values or if they have equivalent values

it's important you understand this. In your code you are asking is my NSNumber object "number" equal to "value", you are not asking does the numerical value stored within my NSNumber object "number" equal the numerical value stored within my NSNumber object "value".

The last line of code you have written shows that in fact your NSNumber's numerical values are in fact equal.

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

So you are correctly storing and retrieving the values, you should be using the == comparison method with NSNumber objects stored numerical values (ie intValue == intValue, unsignedLongLongValue == unsignedLongLongValue) and not comparing their object id's together.

As for this line of code

NSLog(@"Current Number:%@", number); // -2538698204676668398, weird

This is not weird, this is perfectly normal, as you have told NSLog to print out an NSObject representation of 'number'. I'm not 100% certain but I believe that NSNumber's - ( NSString * ) description function defaults to return an unsigned int value for the numerical value it contains. This is why you are getting the large negative number returned. You may want to look at NSNumber's - (NSString *)descriptionWithLocale:(id)aLocale function to print out the data in a more logical for for you, or you could use

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

Which will give you the right answer.

EDIT:

Further to this, after looking into the issue what is happening is that on recollection of your NSNumber object from UserDefaults it's original number type is not being preserved (this information is highlighted in the documentation for NSNumber in the overview section)

(Note that number objects do not necessarily preserve the type they are created with.)

You can see this yourself if you log the following after retrieving "number" from user defaults (add this to the end of the code you have in your question) and have a look at the encoding values shown here

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

The difference between Q and q is that Q is an unsigned value... hence why you are having issues with the isEqualToNumber: function as the number types are different. If you are so dead set on using the iSEqualToNumber: function to compare values then you could implement this to retrieve your value from NSUserDefaults.

NSNumber *number = [NSNumber numberWithUnsignedLongLong:[[NSUserDefaults standardUserDefaults] objectForKey:NumberKey] unsignedLongLongValue]];

You could look at using the NSNumber compare: function to see if the returned value is NSOrderedSame however this will not work for comparing unsigned vs signed values of the same type so in your situation I'd use the above as retrieving the data from NSUserDefaults is stripping the "signedness" of your number.

Stomacher answered 6/10, 2011 at 8:50 Comment(2)
I agree with you. However, "Two NSNumber objects are considered equal if they have the same id values or if they have equivalent values" means that I can depend on the isEqualToNumber: to determine the value of two NSNumber objects, but the result of my code reveals that I cannot depend on this method. I have to deicde by myself that the value type, and use the "XXValue" method and "==" to compare two NSNumber objects, which is not convenient, because isEqual: method is used in many other objects like NSArray, NSDictionary.Package
@Package after looking into this slightly further the nature of your issues is also related to an answer further below. I'll edit my original answer to demonstrate this as well.Stomacher
B
3

At the end of the day if you want to store NSNumber into NSUserDefaults this code works for me even for large integers like: 881217446193276338

To save:

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

To recover:

self.myUser.sessionid = (NSNumber *)[[NSUserDefaults standardUserDefaults] objectForKey:@"sessionid"];
Bradytelic answered 22/9, 2012 at 18:56 Comment(1)
I am glad I wrote this answer, because 6 month later I ran into the same problem, I googled it and landed here and saw my own answer and it again solved my problem :)Bradytelic
B
1

It's storing it correctly, nothing is wrong with your code except:

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

Here number is a non-string object, you might think of it as a wrapper for a numerical primitive. Or you might think that NSNumber instances objectify a primitive type.

What you need is some thing like:

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

Banjermasin answered 6/10, 2011 at 5:39 Comment(1)
What I want to know is that why the number retrieved from NSUserDefaults is not equal to the original number.Package
A
0

Here is a speculative answer:

The NSNumber documentation states that:

(Note that number objects do not necessarily preserve the type they are created with.) .

So it must be using a different internal storage for this type, and only gives you the correct value when you specifically ask for the unsigned long long value. The description method, which is called in your NSLog statement, may be defaulting to a different representation type.

And / or there may be some quirk of the unarchiver that is preventing the isEqualToNumber method working on the value returned from defaults. If you do that comparison between two NSNumbers created in the same scope, does it work? The correct value is definitely in there somewhere given your last statement returns true.

Addicted answered 6/10, 2011 at 7:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.