views:

364

answers:

1

I was reading the apple documentation for memory management, and came across something that I just don't understand. Basically, I don't understand why one does not need need to retain an instance variable through the "getter" method. I wrote this little program to see what would happen. I thought there would be a crash, but I am obviously missing something.

//  main.m
//  Test
//


#import <Foundation/Foundation.h>
#import "Test.h"

int main(int argc, char *argv[])
{
    NSAutoreleasePool *p = [[NSAutoreleasePool alloc] init];

    //Initialize the test object
    Test *t = [[Test alloc] init];

    //Set the value to 5
    [t setMyNum:[NSNumber numberWithInt:5]];

    //Save a temp number that points to the original number
    NSNumber *tempNum = [t myNum];

    //release old number and retain new
    [t setMyNum:[NSNumber numberWithInt:7]];

    //Shouldn't this crash because tempNum is pointing to a deallocated NSNumber???
    NSLog(@"the number is %@",tempNum);

    [p drain];
    return 0;
}

Doesn't tempNum point to a deallocated object??

All help is appreciated.

EDIT

This is the code in the getter and setter methods

#import "Test.h"


@implementation Test
- (void)setMyNum:(NSNumber *)newNum {
    [newNum retain];
    [myNum release];
    myNum = newNum;
}

-(NSNumber *)myNum {
    return myNum;
}
@end

As you can see I am calling release on the old object.

EDIT

It was suggested, and I thought rightfully so that the reason the tempNum is still around is because it hadn't been autoreleased from the pool yet. But even after moving the [pool drain] to right before the NSLog message, there is not crash??? Weird.

+5  A: 

Since you are not explicitly releasing any objects, nothing is being deallocated until the autorelease pool is allowed to drain. Try inserting [p drain] before the last NSLog call. It should crash the NSLog call.

Additionally, if you are not retaining the NSNumber in your setMyNum: method, you will find that it will crash if you add [p drain] before tempNum is assigned.

To clarify the original question, calling a getter method doesn't (and shouldn't) necessarily imply that the caller wants to take ownership (i.e. retain) the variable. If that was the case, this code would leak:

NSLog("Number is %@", [t myNum]);

Also, it appears that NSNumber has an optimization whereby for small numbers, they cache the NSNumber objects, retain an extra copy, and return that version. So for small constants, [NSNumber numberWithInt: N] will return an object with 2 reference counts (available via [theNumber retainCount]). To explicitly see what happens, use a larger constant in the program, an NSNumber will retain a 'fresh' object with a reference count of 1 (that will also be autoreleased).

Jason
But in my setMyNum I am explicitly calling retain and release. What happens to the old number after I call release?
esiegel
release doesn't deallocate the object, it simply subtracts one from the "retain count" of that object. it's only when the retain count hits 0 that the object is deallocated. In your case, the number (NSNumber representing 5) has a retain count of 1 straight after being created, then gets incremented to 2 when it is set in your Test object, then gets decremented back to 1 when another NSNumber (representing 7) gets set in the Test object.
harms
I don't think that this is totally correct:I tried inserting a [p drain] before the NSLog call expecting a crash, but no crash???There must be something else retaining the value??Still confused
esiegel
Well for exploratory purposes (but never for application logic) you can ask an object for it's retain count thus: [someObject retainCount]. There you can explore how it goes up and down as fewer or more other objects have a handle on it.
harms
I think the confusion here is surrounding the autorelease functionality. NSNumber's numberWithInt will return an object that has a reference count of 1 but that will drop to zero (and be deallocated) the next time the autorelease pool is drained (which happens explicitly in this example, or at the end of the event loop when running a Cocoa application). This is why adding [p drain] earlier will crash the application.NSNumber numberWithInt: is essentially short for [[[NSNumber alloc] initWithNumber: 5] autorelease].
Jason
Very Strange, after the first call to setMyNum, the retain count goes from 0 to 3 on myNum. And then only falls to 2 when [pool drain] is called. This is why the program doesn't crash if I call pool drain before the last log. ???
esiegel
Are you sure your getter doesn't have [myNum retain] code in there?
Jason
I copy and pasted.
esiegel
Maybe there is some setting within Xcode that is doing something strange. I don't have garbage collection on. I retried typing in all of this stuff again in a fresh foundation project, and it still has the same behavior.
esiegel
Ok, it looks like NSNumber caches some NSNumber objects (i.e. it keeps them class-level and retains an extra copy of them) for small integers. Try numberWithInt:654321. That will crash it!
Jason
Just tested this in the terminal: Changing the small integers to large ones, and adding the [p drain] before the NSLog call will crash the application.
Jason
Awesome, That is exactly the right answer. How did you come up with it. And can you also add this update to your response so people can see it in the answer.
esiegel
I found more information here: http://forums.macrumors.com/archive/index.php/t-205556.html
Jason