views:

112

answers:

4

I am attempting to wrap my head around one part of the Objective-C memory model (specifically on the iPhone, so no GC). My background is C/C++/Java, and I am having an issue with the following bit of code (also wondering if I am doing this in an "Objective-C way" or not):

- (NSSet *) retrieve
{
    NSMutableSet *set;

    set = [NSMutableSet new];
    // would normally fill the set in here with some data

    return ([set autorelease]);
}

- (void) test
{
    NSSet *setA;
    NSSet *setB;

    setA = [self retrieve];
    setB = [[self retrieve] retain];

    [setA release];
    [setB release];
}

start EDIT

Based on comments below, the updated retrieve method:

- (NSSet *) retrieve
{
    NSMutableSet *set;

    set = [[[NSMutableSet alloc] initWithCapacity:100] autorelease];
    // would normally fill the set in here with some data

    return (set);
}

end EDIT

The above code gives a warning for [setA release] "Incorrect decrement of the reference count of an object is not owned at this point by the caller".

I though that the "new" set the reference count to 1. Then the "retain" call would add 1, and the "release" call would drop it by 1. Given that wouldn't setA have a reference count of 0 at the end and setB have a reference count of 1 at the end?

From what I have figured out by trial and error, setB is correct, and there is no memory leak, but I'd like to understand why that is the case (what is wrong with my understanding of "new", "autorelease", "retain", and "release").

+1  A: 

http://www.macresearch.org/difference-between-alloc-init-and-new

You probably want

NSMutableSet *set = [[NSMutableSet alloc] initWithCapacity: someNumber];

or

NSMutableSet *set = [NSMutableSet setWithCapacity: someNumber];
Azeem.Butt
This is completely irrelevant to the matter at hand. `new` is equivalent to `alloc` + `init`, so the memory management implications are no more complicated.
Chuck
Perhaps you missed the portion of the question which contained the text "also wondering if I am doing this in an "Objective-C way" or not," Chuck.
Azeem.Butt
+4  A: 

I though that the "new" set the reference count to 1. Then the "retain" call would add 1, and the "release" call would drop it by 1. Given that wouldn't setA have a reference count of 0 at the end and setB have a reference count of 1 at the end?

You're leaving out the autorelease. When -(void)test gets a set, its retain count is 0. You don't retain setA, so it already has a retain count of 0 when you try to release it, hence the error message.

The fundamental rule for memory management is quite simple: calls to alloc, new and copy* must be balanced by calls to release/autorelease. The former take ownership, the latter relinquish ownership.

The only tricky part is when dealing with shared objects, where you don't take ownership of an object, so it might be discarded in between the time you get a reference to it and when you use it. This has a simple solution: if in doubt, retain it.

You can make things even simpler by using properties in many situations.

outis
Wish I could select two answers... that yours and chucks both make perfect sense. Thanks!
TofuBeer
+2  A: 

Don't think in terms of absolute numbers. That can be very deceptive. Think of retains and releases as deltas if you must have a number — in this case, the autorelease has already balanced the new (a +1 delta and a -1 delta), so that method manages its memory correctly and the receiver doesn't need to do anything unless it wants to keep the object around longer.

Definitely read the memory management docs. It really is as simple as following the rules described there. It's a very simple contract of ownership where you claim ownership when you want an object to stick around and relinquish ownership when you don't care anymore. In the case above, you relinquish ownership in the retrieve method, so trying to relinquish ownership when you don't have it is obviously a bug.

Chuck
+2  A: 

As the profiler message hints, you should be thinking in terms of ownership. As noted in the memory management rules, whenever you have an object that you have created with +alloc, +new, -copy, or -mutableCopy, you own it and are responsible for releasing it eventually. (In fact, +new is just shorthand for [[MyClass alloc] init].)

-retain takes an object that you didn't initially own and makes you own it.

-release takes an object that you own and releases ownership of it.

-autorelease takes an object that you own and releases ownership of it, but also guarantees that the object will exist for at least a little bit longer.

Your -retrieve method does not transfer ownership of the object it returns. This is good—it follows the memory management rules (the method isn't +alloc, +new, -copy, or -mutableCopy). Therefore, using -release on it without using -retain is an error. It would be equally valid to not retain or release the result from -retrieve, as long as the object will have a temporary lifetime—your -autorelease guarantees temporary existence of the object.

John Calsbeek
Does that mean I should be creating an auto release pool in the test method? Otherwise will setA and setB not ultimately be released until the program ends (since the autorelease pool is setup in main)?
TofuBeer
@TofuBeer: No. In any NSApplication-based program, an autorelease pool is created at the beginning and destroyed at the end of each iteration through the runloop. (This is documented in the memory management docs John linked in the section called "Autorelease Pools", in case you want to read about it.)
Chuck
So, a poor mans GC - thanks :-)
TofuBeer