Why NSNumber points to the same address when value are equals?
Asked Answered
S

3

8

Given the following code:

int firstInt, secondInt;

firstInt = 5;
secondInt = 5;

NSNumber *firstNumber = [NSNumber numberWithInt:firstInt];
NSNumber *secondNumber = [NSNumber numberWithInt:secondInt];

Why on Earth do those two NSNumber instances are pointing to the same address?

This drives me crazy!

Of course, if you change secondInt to, say '4', all works as expected.

Thanks, Jérémy

Swordsman answered 24/11, 2010 at 18:12 Comment(2)
Why do you care if they are the same object vs. two different objects with the same value?Repetitious
Because even though they share the same value, I need them to be separate instances to calculate a score.Swordsman
T
12

This is likely either a compiler optimisation or an implementation detail: as NSNumber is immutable there's no need for them be separate instances.

EDIT: probably an implementation optimisation thinking about it. Likely numberWithInt returns a singleton when called subsequently with the same integer.

Taipan answered 24/11, 2010 at 18:16 Comment(5)
IIRC NSNumber has an internal optimisation that returns singletons for the lower integers. In practice it shouldn't matter because it is the value that is important and not the memory address.Stedmann
My problem is that I need to add those int to an array, sort them (by their value) and retrieve their index (inside the array). And because of the singleton, I get the wrong index.Swordsman
Interesting and worth knowing, thanks Abizern and Dave. jchatard: that sounds like it should be a follow-up question.Taipan
@Swordsman The quick and dirty solution is to create an NSObject subclass with a single NSInteger property. And use that instead of NSNumber to add the score to the array. A bit more work than using NSNumber, but it will do what you want. The better solution is to think about your design again.Stedmann
Abizern's dead on. If these numbers have some sort of meaning, perhaps you shouldn't be representing them as NSNumbers but some other object instead.Carrell
S
6

My design instinct tells me that, if the score's identity is important as distinct from its mere value, you should be sorting some kind of score object instead of plain NSNumbers.

But that aside: In a pinch, you can use plain NSValue similarly to how you're using NSNumber. It's a little more work to get values out, but NSValue itself doesn't have the instance coalescing behavior NSNumber does for small values.

Some code that exercises all three behaviors:

  // NSValues are always distinct:
  int foo = 5, bar = 5, outfoo, outbar;
  NSValue *one = [NSValue value:&foo withObjCType:@encode(int)];
  NSValue *two = [NSValue value:&bar withObjCType:@encode(int)];

  [one getValue:&outfoo];
  [two getValue:&outbar];
  NSLog(@"one: %@ %x = %d ; two: %@ %x = %d",
        [one class], one, outfoo,
        [two class], two, outbar);

  // by comparison with NSNumber behavior:
  NSNumber *three = [NSNumber numberWithInt:6];
  NSNumber *four = [NSNumber numberWithInt:6];

  NSLog(@"three: %@ %x = %d ; four: %@ %x = %d",
        [three class], three, [three intValue],
        [four class], four, [four intValue]);

  // except when the numbers are big:
  NSNumber *five = [NSNumber numberWithInt:8675309];
  NSNumber *six = [NSNumber numberWithInt:8675309];

  NSLog(@"five: %@ %x = %d ; six: %@ %x = %d",
        [five class], five, [five intValue],
        [six class], six, [six intValue]);

On my mac this yields output like:

one: NSConcreteValue 42a8d0 = 5 ; two: NSConcreteValue 42a920 = 5
three: NSCFNumber 404380 = 6 ; four: NSCFNumber 404380 = 6
five: NSCFNumber 1324d0 = 8675309 ; six: NSCFNumber 106e00 = 8675309
Sabbat answered 24/11, 2010 at 19:32 Comment(2)
Looks like the implementation for storing big numbers has been improved over the years. For the snippet NSNumber *five = [NSNumber numberWithInt:8675309]; NSNumber *six = [NSNumber numberWithInt:8675309]; NSLog(@"five: %@ %x = %d ; six: %@ %x = %d", [five class], five, [five intValue], [six class], six, [six intValue]); I get the output with the same addresses: five: __NSCFNumber 845fed27 = 8675309 ; six: __NSCFNumber 845fed27 = 8675309Lobscouse
I think that's due to the "tag pointers" that came with the 64-bit environment: mikeash.com/pyblog/… . TLDR: any NSNumber shorter than 64 bits minus the tagging overhead, can now be a "singleton instance" (which is really just the pointer value itself.) [Note how your pointer value is an odd number, which true pointers can't be]Sabbat
G
3

Because they have the same hash value, so NSNumber gives back to you a cached pointer from the first allocation.

Gilded answered 24/11, 2010 at 18:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.