PDA

View Full Version : Question about object alloc/init related crash




mduser63
Jun 23, 2006, 01:57 AM
I'm seeing something weird that I can't figure out. I've just done the challenge at the end of Chapter 15 in Aaron Hillegass' Cocoa Programming for Mac OS X. You're supposed to write a document-based app that will allow the user to draw ovals. Anyway, I've got it working, but I'm seeing something that I don't understand. In it the initWithFrame: method for the view implementation, really the only thing that gets done is allocation and initialization of the NSMutableArray instance variable (ovals) used to hold the NSBezierPath's for the ovals. The code below causes no problems:

- (id)initWithFrame:(NSRect)frameRect
{
if ((self = [super initWithFrame:frameRect]) != nil) {
// Add initialization code here
ovals = [[NSMutableArray alloc] initWithCapacity: 1];
}
return self;
}

However, this code crashes immediately upon launching the application. If I run it from XCode, the debugger comes up immediately. (Either I don't know how to use the debugger, or you have to be some kind computer brain to use it, because the information it gives is really rather useless to me.)

- (id)initWithFrame:(NSRect)frameRect
{
if ((self = [super initWithFrame:frameRect]) != nil) {
// Add initialization code here
ovals = [NSMutableArray arrayWithCapacity: 1];
}
return self;
}


Anyway, does anyone know why using an explicit alloc then an initWithCapacity: method as opposed to the arrayWithCapacity: makes a difference? I'm still having a pretty hard time getting my head around all the reference counting, retain/release and autorelease pool stuff in Objective-C.

I've uploaded the project here (http://www.villainousturtle.com/misc/chap15.zip) in zip format if you want to download it. The code in that project right now is in "crash" state. If it matters, I'm doing all this on an Intel Mac.

(I realize that I may not have done everything else in my code the best way or the way it should be done, but right now I'm really only concerned with figuring out the problem described here.)



HexMonkey
Jun 23, 2006, 02:16 AM
The arrayWithCapacity method is just a convenience method which is basically equivalent to [[[NSMutableArray alloc] initWithCapacity: 1] autorelease]. So in your second code fragment, the ovals object is added to the autorelease call and is released shortly after the OvalView is created (resulting in a retain count of 0). Then when it's used, it's already been deallocated, hence the crash.

mduser63
Jun 23, 2006, 02:23 AM
Wow you're fast HexMonkey. I figured I'd get an answer in the morning (it's after 1:00 AM here).

Am I correct that the solution (other than using the first code snippet) is to send a retain message to ovals then explicitly send it a release method in my (overridden) dealloc method? I don't quite understand why it gets released, as it seems like I haven't done anything different in other code that works fine. The retain count is 1 right after it is initialized. When does it get a release message causing the retain count to go down to 0 (causing it to be released)?

HexMonkey
Jun 23, 2006, 02:47 AM
Wow you're fast HexMonkey. I figured I'd get an answer in the morning (it's after 1:00 AM here).

Am I correct that the solution (other than using the first code snippet) is to send a retain message to ovals then explicitly send it a release method in my (overridden) dealloc method? I don't quite understand why it gets released, as it seems like I haven't done anything different in other code that works fine. The retain count is 1 right after it is initialized. When does it get a release message causing the retain count to go down to 0 (causing it to be released)?

Adding a retain message would solve the problem, but it's not as elegant a way to do it as in the first code fragment since you're effectively allocating, retaining then releasing the object rather than just allocating it.

It gets released because arrayWithCapacity is a convenience constructor, so by convention it returns an autoreleased object. The object is then released when leaving the scope of the current autorelease pool, typically done in the event loop (which is handled by the OS).

You might want to read relevant sections of the following pages to make a bit more sense of this:
Object Ownership and Disposal (http://developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgmt/Concepts/ObjectOwnership.html#//apple_ref/doc/uid/20000043-BEHDEDDB)
Autorelease Pools (http://developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgmt/Concepts/AutoreleasePools.html)

HiRez
Jun 23, 2006, 03:15 AM
Am I correct that the solution (other than using the first code snippet) is to send a retain message to ovals then explicitly send it a release method in my (overridden) dealloc method? I don't quite understand why it gets released, as it seems like I haven't done anything different in other code that works fine. The retain count is 1 right after it is initialized. When does it get a release message causing the retain count to go down to 0 (causing it to be released)?You are correct in your assumptions. The reason the second code fragment fails is that you're using a convenience initializer, which is a convention in Cocoa (usually of the form xxxOfxxx, such as stringWithCString, arrayWithCapacity, etc. ) that, as Hex mentions, is already autoreleased. Autoreleased meaning that the runtime system is going to send the object a release message the next time it completes an event loop. This means it'll be done "behind your back", so if you want to hold on to that object, you'd better send it a retain message.

It's tricky because you just have to know which methods these convenience initializers are (usually any initializer that does not start with "init"). The documentation generally does not tell you on a method-by-method basis. So using convenience initializers, you don't need to explicitly retain the object. Usingy either initialization method, you still must send the object a release message in your dealloc method in any case, unless it's just a temporary object that's only being used within the scope of a local method, in which case you'd either send it a release message as soon as you're done with it in the same method (if you used alloc-init), or just let the runtime deallocate it in the autorelease pool (if you used a convenience initializer or used alloc-init with an explicit autorelease message). One other note, only use alloc with init, never use alloc with a convenience initializer (it's implicitly doing an alloc).

To summarize:

1. If you create a subobject that you want to keep around in for the life of your main object (the one you're creating, in your initXxx or sometimes awakeFromNib methods), use either alloc-init, or use a convenience contructor with retain. Always release such objects in your dealloc method in any case.

2. If you only need an object briefly, do the initialization and the deallocation in the same scope. Make sure that when your program changes scope (for example, completes a for loop), you've taken care of releasing that object as appropriate.

Using alloc-init:int i;
for (i = 0; i < 10; i++) {
NSNumber *n = [[NSNumber alloc] initWithInt:i];
NSLog(@"The number is %d", [n intValue]);
[n release]; /* done with n,release it or you leak this memory */
} /* it's too late to do it after here because you created it inside the loop */Using a convenience initializer:int i;
for (i = 0; i < 10; i++) {
NSNumber *n = [NSNumber numberWithInt:i]; /* convenience initializer */
NSLog(@"The number is %d", [n intValue]);
} /* no need to release it; you created it with a convenience initializer, it's autoreleased and will be deallocated at some time in the future (you should not care about when in most cases) */

mduser63
Jun 23, 2006, 12:17 PM
Thanks for the replies! I read both of the documents that HexMonkey linked to, and that helped a lot. My information about reference counting and autorelease pools came from Programming in Objective-C, and while the information in that book is fine within the scope of the programs in the book (all command-line, non-AppKit), it doesn't give an accurate enough description for the move to Cocoa. For example, I didn't understand the scope of autorelease pools at all. Anyway, thanks for the help!