Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

mduser63

macrumors 68040
Original poster
Nov 9, 2004
3,042
31
Salt Lake City, UT
I'm working my way through Stephen Kochan's Programming in Objective-C right now, and I've just finished Chapter 17 on memory management. The first exercise is to write a program to determine the effect on reference counts of adding and removing objects in a dictionary object. Anyway, I've got the following code:

Code:
#import <Foundation/Foundation.h>

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
	NSNumber *num1, *num2, *num3;
	NSString *string1, *string2, *string3;
	NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithCapacity: 3];
	
	num1 = [NSNumber numberWithInt: 1];
	num2 = [NSNumber numberWithInt: 2];
	num3 = [NSNumber numberWithInt: 3];
	
	string1 = [NSString stringWithString: @"first string"];
	string2 = [NSString stringWithString: @"second string"];
	string3 = [NSString stringWithString: @"third string"];
	
	printf("\n   num1 retain count before dictionary: %x\n", [num1 retainCount]);
	printf("string1 retain count before dictionary: %x\n", [string1 retainCount]);
	printf("   num2 retain count before dictionary: %x\n", [num2 retainCount]);
	printf("string2 retain count before dictionary: %x\n", [string2 retainCount]);
	printf("   num3 retain count before dictionary: %x\n", [num3 retainCount]);
	printf("string3 retain count before dictionary: %x\n", [string3 retainCount]);

    [pool release];
    return 0;
}

When I compile and run it, I get the following:

num1 retain count before dictionary: 2
string1 retain count before dictionary: 1
num2 retain count before dictionary: 2
string2 retain count before dictionary: 1
num3 retain count before dictionary: 2
string3 retain count before dictionary: 1

Can anyone tell me why the NSNumber objects have a reference count of 2 after creation, while the strings only have a count of 1? I expected both types of objects to have a reference count of 1 after initialization.
 

HexMonkey

Administrator emeritus
Feb 5, 2004
2,240
504
New Zealand
I think it's just some internal optimization that NSNumber does. Since NSNumbers are not mutable, it just returns the same object whenever initialised with certain common values.

This might be better illustrated with an example:

Code:
NSNumber *num1, *num2;
num1 = [NSNumber numberWithInt:1];
NSLog(@"num1 retain count after setting to 1: %d", [num1 retainCount]);
num2 = [NSNumber numberWithInt:1];
NSLog(@"num1 retain count after setting num2 to 1: %d", [num1 retainCount]);
NSLog(@"Address of num1: %p", num1);
NSLog(@"Address of num2: %p", num2);
		
num1 = [NSNumber numberWithInt:1000];
NSLog(@"num1 retain count after setting to 1000: %d", [num1 retainCount]);
num2 = [NSNumber numberWithInt:1000];
NSLog(@"num1 retain count after setting num2 to 1000: %d", [num1 retainCount]);
NSLog(@"Address of num1: %p", num1);
NSLog(@"Address of num2: %p", num2);
This code produces the following output:
Code:
num1 retain count after setting to 1: 2
num1 retain count after setting num2 to 1: 3
Address of num1: 0x3068c0
Address of num2: 0x3068c0

num1 retain count after setting to 1000: 1
num1 retain count after setting num2 to 1000: 1
Address of num1: 0x3073d0
Address of num2: 0x3073e0
In the first section num1's retain count is changed when num2 is created with the same value (1). In fact, when their addresses are printed they are the same, so NSNumber is just returning the same object.

In the second section, num1's retain count is not changed, as NSNumber returns different objects for num1 and num2. This shows that the optimizations are only done for more common numbers.
 

mduser63

macrumors 68040
Original poster
Nov 9, 2004
3,042
31
Salt Lake City, UT
Thanks for the reply HexMonkey. That makes sense. I'm quite new at this, but I assume that since they're not mutable, it doesn't matter of num1 and num2 are really the same object because the reference counting mechanism ensures that even if num1 is no longer needed and is released the underlying object sticks around until num2 is also no longer needed. Is that correct?

Also, I'm thinking that the reason an NSNumber object with an int value of 1 starts out with a retain count of 2 is because code that's part of the system (nothing I wrote) is already using an NSNumber object with a value of 1. Does that sound right?
 

HexMonkey

Administrator emeritus
Feb 5, 2004
2,240
504
New Zealand
Yeah, you've got the idea. I suspect what's happening internally is that NSNumber is creating all the common number objects the first time it's called, so that it can easily just return pointers to these objects on demand. This would explain why the numbers start off with retain counts of 2.
 

HiRez

macrumors 603
Jan 6, 2004
6,265
2,630
Western US
Something is still fishy here. Try this: change num1 = [NSNumber numberWithInt:1]; to num1 = [NSNumber numberWithFloat:1.0]; and the retain count becomes 1 instead of 2. And when I tried num1 = [NSNumber numberWithBool:YES];, the retain count was 2147483647 (which is the maximum value of a 32-bit signed integer). WTF? Quite confusing.
 

HexMonkey

Administrator emeritus
Feb 5, 2004
2,240
504
New Zealand
HiRez said:
Something is still fishy here. Try this: change num1 = [NSNumber numberWithInt:1]; to num1 = [NSNumber numberWithFloat:1.0]; and the retain count becomes 1 instead of 2.

That makes sense, floats aren't used nearly as much as ints so it probably doesn't cache them. And of course, a float with value 1 and an int with value 1 are quite different internally.

HiRez said:
And when I tried num1 = [NSNumber numberWithBool:YES];, the retain count was 2147483647 (which is the maximum value of a 32-bit signed integer). WTF? Quite confusing.

That's strange, it looks like it's doing something non-standard. It's retain count doesn't even change if you send it retain or release messages. Perhaps it's a safeguard to make sure it's never unallocated.
 

HiRez

macrumors 603
Jan 6, 2004
6,265
2,630
Western US
OK, so after some more testing, yes it does appear the system is retaining a single immutable object for numbers re-used.
Code:
NSNumber *num1 = [NSNumber numberWithInt:1];
NSLog(@"num1 retain count = %d", [num1 retainCount]);
NSNumber *num2 = [NSNumber numberWithInt:1];
NSLog(@"num2 retain count = %d", [num2 retainCount]);
NSNumber *num3 = [NSNumber numberWithInt:1];
NSLog(@"num3 retain count = %d", [num3 retainCount]);
yields:

2006-05-31 23:29:35.018 RetainCountTest[1309] num1 retain count = 2
2006-05-31 23:29:35.019 RetainCountTest[1309] num2 retain count = 3
2006-05-31 23:29:35.019 RetainCountTest[1309] num3 retain count = 4


But I still can't explain the Boolean test. It seems like maybe it's not even initialized, maybe when you call for an NSNumber representing a Boolean, it intercepts that and knows it doesn't need to store a value.
 

HiRez

macrumors 603
Jan 6, 2004
6,265
2,630
Western US
HexMonkey said:
It's retain count doesn't even change if you send it retain or release messages. Perhaps it's a safeguard to make sure it's never unallocated.
Ah yes, maybe...although if calls to release: don't do anything anyway I'm not sure why it's needed. Maybe the system has privileges to send release messages where normal user apps don't. Anyway, I'm glad you asked the question, mduser, because I'm sure someday I would have hit upon this issue and it would have confused me.
 

caveman_uk

Guest
Feb 17, 2003
2,390
1
Hitchin, Herts, UK
What's more odd is that these objects have never actually been retained. They're not created using copy or alloc so I would have thought the retain count would be 0.

Wierd.
 

HexMonkey

Administrator emeritus
Feb 5, 2004
2,240
504
New Zealand
caveman_uk said:
What's more odd is that these objects have never actually been retained. They're not created using copy or alloc so I would have thought the retain count would be 0.

I think it's correct, NSNumber would retain them, autorelease them and then return them. They would then be released at a later time when the autorelease pool is emptied.

Remember that numberWithInt etc are just convenience methods that call their respective init methods, eg:

Code:
+(NSNumber*)numberWithInt:(int)value
{
	return [[[self class] initWithInt:value] autorelease];
}
 

JonDann

macrumors member
Mar 4, 2006
39
0
Birmingham, UK
Thanks guys, I came across this today when looking to see what the retain count of an NSNumber boolean was! I spent all night trying to work it out, writing examples to try and see what was going on.

You guys rock!
 

Krevnik

macrumors 601
Sep 8, 2003
4,101
1,312
What's more odd is that these objects have never actually been retained. They're not created using copy or alloc so I would have thought the retain count would be 0.

Wierd.

Autoreleased objects will keep a retain count of 1 until the pool is released. That is because interally, all objects MUST be created via alloc or copy, and the pool is used to delay the release message. It doesn't actually change the retain count until the delayed release occurs.
 

Gelfin

macrumors 68020
Sep 18, 2001
2,165
5
Denver, CO
There is some cleverness going on inside the Foundation here. NSNumber is the interface you see, but there are a handful of private classes doing things behind the scenes. From the little bit of examination I've been able to do, it looks as if you will get "shared" versions of integer NSNumbers for values in the range [0-12]. Anything larger than 12 gets you a unique instance even if the values are equal. Why twelve? No clue. I don't even know if that's a hard number or circumstantial.

When you call numberWithInt what you get is actually an NSCFNumber, which I gather would be the implementation for the "toll-free bridging" between NSNumber and CFNumber you'll read about in the documentation.

Calling numberWithBool returns you a special case, an NSCFBoolean. It appears the Foundation keeps a static YES and NO object in its own memory space, and returns one or the other of those as appropriate, so that there is only ever one instance of YES and NO as NSNumbers in use. The weird results you get when trying to peer into a boolean NSNumber would be a result of this special case.

I am curious now whether setting the retain count of any object to INT_MAX makes it immortal (which would be a bad design idea, so I expect not), whether there is an "immortal" flag on NSNumber that causes retainCount to always return INT_MAX, or if NSCFBoolean just overrides retainCount. Unfortunately I don't have time to find out right now. :-/
 

Catfish_Man

macrumors 68030
Sep 13, 2001
2,579
2
Portland, OR
There is some cleverness going on inside the Foundation here. NSNumber is the interface you see, but there are a handful of private classes doing things behind the scenes. From the little bit of examination I've been able to do, it looks as if you will get "shared" versions of integer NSNumbers for values in the range [0-12]. Anything larger than 12 gets you a unique instance even if the values are equal. Why twelve? No clue. I don't even know if that's a hard number or circumstantial.

When you call numberWithInt what you get is actually an NSCFNumber, which I gather would be the implementation for the "toll-free bridging" between NSNumber and CFNumber you'll read about in the documentation.

Calling numberWithBool returns you a special case, an NSCFBoolean. It appears the Foundation keeps a static YES and NO object in its own memory space, and returns one or the other of those as appropriate, so that there is only ever one instance of YES and NO as NSNumbers in use. The weird results you get when trying to peer into a boolean NSNumber would be a result of this special case.

I am curious now whether setting the retain count of any object to INT_MAX makes it immortal (which would be a bad design idea, so I expect not), whether there is an "immortal" flag on NSNumber that causes retainCount to always return INT_MAX, or if NSCFBoolean just overrides retainCount. Unfortunately I don't have time to find out right now. :-/

The implementation of retain and release is Really Odd® and only odder in Leopard (sadly the CF source for leopard hasn't been posted yet, so I'm going on hearsay and limited testing). Fun example: write a benchmark that measures how long -retain takes in a loop. I suspect you'll find that it's only linear up to a certain retain count :)
 

HiRez

macrumors 603
Jan 6, 2004
6,265
2,630
Western US
Haha, funny to see this old thread pop up again. Anyway, if they're doing some internal optimization cleverness, doesn't it make sense for them to also change the external representation of the retain counts to match the expected values? It's not too developer-friendly when you're trying to track down a bug and get all this weird, undocumented behavior.
 

Catfish_Man

macrumors 68030
Sep 13, 2001
2,579
2
Portland, OR
fwiw I hacked up a quick retain benchmark, but the data was way too noisy to see what I was expecting. I'm extremely unclear on why, although I suspect my timings weren't accurate enough (I was using NSDate rather than mach_absolute_time).

<edit>
The thing I was thinking of only applies to CFRetain anyway, and behaves slightly differently than I had thought. Please disregard my cryptic and ultimately incorrect meanderings :)
</edit>
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.