PDA

View Full Version : Beginner: Aaron Hillegass Retain/Release Question




czeluff
Sep 1, 2007, 06:04 PM
Hello, i'm working out of Aaron Hillegass' book (very popular around here). I'm only on Chapter 3, but I already have questions regarding retain/releasing properly.

In my lottery.m main() method I have the following loop:

for (i=0; i < 10; i++)
{
// Create a new instance of LotteryEntry
newEntry = [LotteryEntry new];
[newEntry prepareRandomNumbers];

// Create a date/time object that is i weeks from now
[newEntry setEntryDate: [now dateByAddingYears:0 months:0 days:(i*7) hours:0 minutes:0 seconds:0]];

// Add the LotteryEntry object to the array
[array addObject:newEntry];

// Decrement the retain count of the lottery entry
[newEntry release];
}

My LotteryEntry.m's setEntryDate() method looks like this:

- (void)setEntryDate:(NSCalendarDate *)date
{
[date retain];
[entryDate release];
[date setCalendarFormat:@"%b %d, %Y"];
entryDate = date;
}

This does NOT make logical sense at all. Here's what I don't understand.

1. WHY do I do [date retain]? What's the point of increasing the retain value here?

2. Since the for-loop will call setEntryDate() 10 times, i'm releasing entryDate 10 times. How is that POSSIBLE, considering that I haven't called retain on entryDate 10 times.

Thanks,

cz



robbieduncan
Sep 1, 2007, 07:09 PM
The Cocoa convention is that methods that receive objects must take responsibility for retaining any objects they want to keep around: you should always assume objects passed to you have retain count 0, or more likely have been autoreleased. In your specific example the dateByAddingYears:days:hours:minutes:seconds: method will have returned an autoreleased object (as it is not an init or copy method, another convention) so if you don't retain it the object will get freed at the end of this runloop.

DannySmurf
Sep 1, 2007, 07:14 PM
Well, you've not included all of the relevant code, but I'll take a stab:

1. The value you are passing into the date parameter is probably being autoreleased by the dateByAddingYears function, in which case your code needs to retain it as early as possible (and inside setEntryDate is the first opportunity you have in this case) so that it does not get reclaimed after it goes out of scope.

2. Is the entryDate variable created in the LotteryEntry constructor? If so, then releasing it in that context makes sense, because you actually are creating 10 separate objects that need to eventually be released (once each time you run through the loop).

czeluff
Sep 1, 2007, 07:35 PM
ok, yes, i think that makes sense no. Thanks you guys.

But now another question has popped into my head. The author of this book wrote the dealloc method also, seen here:

- (void)dealloc
{
NSLog(@"Destroying %@", self);
[entryDate release];
[super dealloc];
}

now im REALLY confused, because now entryDate is being released a total of TWENTY times!!! (since dealloc is called whenever the retain value = 0). It's called ten times inside of setEntryDate, and ten times for each dealloc call, right?

Sorry, this really isnt making much sense for me.

p.s. @interface LotteryEntry : NSObject
{
NSCalendarDate *entryDate;
int firstNumber;
int secondNumber;
}

There is where entryDate is created.

robbieduncan
Sep 1, 2007, 08:26 PM
entryDate is simply a variable, the instances of NSCalendarDate that are being pointed at are what are being released, and these are all different objects.

Also that interface declares a variable that has enough storage space to point at an instance of NSCalendarDate. It does not create an instance of that class.

czeluff
Sep 1, 2007, 08:34 PM
please read my next post

czeluff
Sep 1, 2007, 08:41 PM
i still dont get it!

Here is LotteryEntry.h

#import <Foundation/Foundation.h>


@interface LotteryEntry : NSObject
{
NSCalendarDate *entryDate;
int firstNumber;
int secondNumber;
}

- (void)prepareRandomNumbers;
- (void)setEntryDate:(NSCalendarDate *)date;
- (NSCalendarDate *)entryDate;
- (int)firstNumber;
- (int)secondNumber;
@end


Here is LotteryEntry.m

#import "LotteryEntry.h"


@implementation LotteryEntry

- (void)prepareRandomNumbers
{
firstNumber = random() % 100 + 1;
secondNumber = random() % 100 + 1;
}

- (void)setEntryDate:(NSCalendarDate *)date
{
[date retain];
[entryDate release];
[date setCalendarFormat:@"%b %d, %Y"];
entryDate = date;
}

- (NSCalendarDate *)entryDate
{
return entryDate;
}

- (int)firstNumber
{
return firstNumber;
}

- (int)secondNumber
{
return secondNumber;
}

- (NSString *)description
{
return [NSString stringWithFormat:@"%@ = %d and %d", entryDate, firstNumber, secondNumber];
}

- (void)dealloc
{
NSLog(@"Destroying %@", self);
[entryDate release];
[super dealloc];
}

@end


Here is lottery.m

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

int main (int argc, const char * argv[])
{
NSMutableArray *array;
int i;
LotteryEntry *newEntry;
LotteryEntry *entryToPrint;

NSCalendarDate *now;
NSAutoreleasePool *pool = [NSAutoreleasePool new];

// Create the date object
now = [NSCalendarDate new];

// Initialize the random number generator
srandom(time(NULL));
array = [NSMutableArray new];

for (i=0; i < 10; i++)
{
// Create a new instance of LotteryEntry
newEntry = [LotteryEntry new];
[newEntry prepareRandomNumbers];

// Create a date/time object that is i weeks from now
[newEntry setEntryDate: [now dateByAddingYears:0 months:0 days:(i*7) hours:0 minutes:0 seconds:0]];

// Add the LotteryEntry object to the array
[array addObject:newEntry];

// Decrement the retain count of the lottery entry
[newEntry release];
}

for(i=0; i < 10; i++)
{
// Get an instance of LotteryEntry
entryToPrint = [array objectAtIndex:i];

// Display its content
NSLog(@"entry %d is %@", i, entryToPrint);
}

[array release];

//Release the current time
[now release];
[pool release];
return 0;
}



Someone please tell me WHY the dealloc() method has [entryDate release] in it. It seems completely pointless, since i [entryDate release] every single time i call setEntryDate().

Also, WHY is dealloc() run ten times at the end of the program? Shouldn't it be called every single time that i [entryDate release]???

DannySmurf
Sep 1, 2007, 08:58 PM
First, you really would have to ask the original developer for detailed answers to these questions, since the person who wrote the code is the only one who REALLY knows how it works.

Second, whether you call release or dealloc once or ten times or a hundred times doesn't really matter from a code point of view. As long as you call release at least once on each object that was allocated (and as long as each of these calls actually get executed), you can be satisfied that your application will not leak memory. Since you can send a message to a nil object, you actually can call release/dealloc on an object as many times as you want. Only the first one will have any effect. Each subsequent call just gets sent off into oblivion.

The short short answer is you're probably worrying about this too much. If it were your code and you still didn't understand what it was doing, that would be a cause for concern. But your responsibility is to make sure that your own code works properly and manages its memory properly; the developer who originally wrote this code has already done that (I assume so, anyway... but then, I've seen books that got things horribly wrong too).

czeluff
Sep 1, 2007, 09:10 PM
thank goodness Obj-C 2.0 is right around the corner, along with garbage collection. I was always really good with Java, and horrible with C. I think memory is what I just don't get.

cz

savar
Sep 1, 2007, 10:15 PM
Someone please tell me WHY the dealloc() method has [entryDate release] in it. It seems completely pointless, since i [entryDate release] every single time i call setEntryDate().

Also, WHY is dealloc() run ten times at the end of the program? Shouldn't it be called every single time that i [entryDate release]???

The short answer is that it has release in the dealloc because if it didn't, it would leak that object.

I think your confusion is with this code:

- (void)setEntryDate:(NSCalendarDate *)date
{
[date retain];
[entryDate release];
[date setCalendarFormat:@"%b %d, %Y"];
entryDate = date;
}

The thing to keep in mind is that "entryDate" is just a pointer to an object...it isn't an object itself.

So [date retain] means "retain the date object that was passed to me".

[entryDate release] means "release the object currently pointed to by entryDate".

entryDate=date means "make the pointer called 'entryDate' point to the same object that 'date' points to.

So even though you literally see [entryDate release], at the end of this function entryDate points to an object which has at least +1 reference count, because entryDate is now pointing to a different object.

The author probably did this because they wanted to be able to call setEntryDate more than once for the same LotteryEntry. So the code has to release any entryDate if it has one already existing, and then retain the new entry date and stuff it into the same variable as the old one. This is a pretty common Cocoa pattern, so it's good for you to understand this.

As to your reference to garbage collection...I still like Cocoa's memory management better than Java's. You get deterministic control over what happens, and the system is actually very simple. The retain/release stuff is dead easy, and the only confusing part (from my perspective) is learning the convention, which can be summarized as this:

1) Any object you create is implicitly retained. This makes sense, because if an object was created with reference count equal to zero, it shouldn't exist.

Creating an object means you explicitly call [[<NSObject alloc] init] or you use one of the convenience methods that a lot of the classes provide.

2) Any object created by somebody else ("Somebody" means a foreign object, not literally another person besides yourself, of course) has no guarantees. It might be retained, it might not be.

Therefore, if you know that you want to keep that object, you need to retain it. Or if you just want to use it in the current scope, then no need to worry. (Autoreleases are performed at the end of the event loop, if I recall correctly -- my cocoa is rusty -- so you can actually use that pointer in more than just the "current scope". You could pass it to another function and use it there as well as long as it was still within the same event loop.)

So if you get an element out of an NSArray, for example, I can tell you that the NSArray has definitely retained every object that is inside of it. BUT if you get a pointer to that object that you want to keep and use, then you need to retain it. Think about what would happen if that object was removed from the array and you hadnt retained it yet: NSArray would release that object, and if you hadn't retained it would get destroyed, and then your program would crash a few milliseconds later.

DannySmurf
Sep 2, 2007, 12:38 PM
thank goodness Obj-C 2.0 is right around the corner, along with garbage collection. I was always really good with Java, and horrible with C. I think memory is what I just don't get.

I'm with you. I'm mainly a C# programmer. I can do memory management, but I absolutely hate it. I'll be happy when this whole thing is garbage collected too.

Columbo X
Sep 3, 2007, 04:11 PM
Hi czeluff,

The important thing about the setEntryDate method is the call to release. As savar mentions above, this is needed here to avoid memory leaks.

In the code you show above, you only call setEntryDate once for each LotteryEntry instance. In each case, entryDate will (or should) initially be null and so the release will have no effect as there is no entryDate to dispose of. Only if you called setEntryDate more than once does this release become relevant (in order to avoid memory leaks).

Regarding your other question about the 10 deallocs, this occurs when the [array release] line is executed. In main() you enter the for loop to create your LotteryEntry objects. The line

newEntry = [LotteryEntry new]

creates a new object with retain count of 1. When you later call the line...

[array addObject:newEntry]

array will send a retain message so the retain count of your newEntry object now goes to 2. At the end of the loop, main() no longer wants to share ownership so

[newEntry release]

is called. This decrements the retain count to 1 (that is 1 object owns newEntry now - this is the array).

When the loop starts again, the local variable newEntry will point to another block of memory representing a totally separate LotteryEntry instance.

After your second (printout) loop completes, you call

[array release]

The array object sends release to all contained objects - that is your 10 LotteryEntry instances created above. Since they all had retain counts of 1, release will decrease these to 0. When this happens the dealloc gets called (10 times - once per LotteryEntry instance). You mention dealloc gets called 20 times. The other 10 are when the entryDate is released in the dealloc method in LotteryEntry.

The problems I generally have are remembering when an object is autoreleased or not. If you're using the second edition of Hillegass' book, the rules governing what is autoreleased or not are covered on page 64.

Hope this helps!