Adventures in the transition from C to Cocoa.

Showing posts with label Memory Management. Show all posts
Showing posts with label Memory Management. Show all posts

Tuesday, June 26, 2007

NSAutoReleasePool

NSAutoReleasePool is Memory Management object used in Cocoa. They act as a catch-all for allocations, cleaning up memory when objects are no longer needed.

Instead of sticking strictly to retain and release, these pools allow us one more memory management method: autorelease. This method puts our object in the most recently-created pool. When these pools get freed, all the objects in them get freed as well. Because of this behavior, it's common to put these pools around loops and inside functions that perform several allocations. It is common to create a pool right from the beginning in main(), as a catch-all for your entire application.

Additionally, Cocoa's Frameworks often require a pool to be available for things to work correctly. Without them, the program will probably still run, but it will generate Tons of warnings like this at runtime:


cwright@phendrana:~/projects/addr>./main.broken
2007-06-26 20:43:51.068 main.broken[3819] *** _NSAutoreleaseNoPool(): Object 0x1802000 of class NSCFDictionary autoreleased with no pool in place - just leaking


This is Cocoa's way of telling you that you need an autorelease pool in place to prevent leaks.

Using autorelease pools is quite simple: NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; sets up a pool. [pool release] cleans it up. They can be nested, so that a function can create one, and then a loop in that function can create its own as well. Nested pools get released automatically when their parent pool is released. This is probably very sloppy, but it appears to work.

Here's some sample code that shows this behavior, as well as usage of NSAutoReleasePool.


#import <Foundation/Foundation.h>

@interface OurObject: NSObject
-(void)dealloc;
@end

@implementation OurObject
-(void)dealloc
{
printf("Dealloc on OurObject\n");
[super dealloc];
}
@end

void createAPoolAndLeak()
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
OurObject *oo = [[OurObject alloc] init];

// add to this function's pool...
[oo autorelease];
// ... and forget to release the pool! *Gasp*
}

void createAPoolAndDontLeak()
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
OurObject *oo = [[OurObject alloc] init];

// add to this function's pool...
[oo autorelease];
// ... and release the pool!
[pool release];
}

int main()
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

printf("Creating a pool and not leaking...\n");
createAPoolAndDontLeak();
printf("Pool cleaned up.\n");
printf("Creating a leaky pool\n");
createAPoolAndLeak();
printf("Leaky pool created.\n");

printf("Releasing parent pool...\n");
[pool release];

printf("Pool is cleaned up...\n");
return 0;
}


Using these pools is disappointingly simple. They really don't require much work at all.

Monday, June 18, 2007

Retain and Release, and Object Creation

Objects in Objective-C maintain a counter to manage how many object point to the object in question. This counter modified by two methods, retain and release, and can be accessed directly by the retainCount method.

Each call to [object retain]; increments this counter, and each call to [object release]; decrements it. The retain/release counter starts at 1. When an object's count drop to 0, it deallocates itself. In Memory Management documentation, you'll find this behavior referred to as Reference Counting.

When an object deallocates itself, it uses its dealloc method. This method is comparable to a destructor in C++. Like C++, dealloc takes no arguments, and has no return value.

To create an object, you've probably noticed the [[object alloc] init]; sequence used. This uses two methods, alloc and init. alloc simply allocates memory. It is essentially the same as malloc() in C. init is used to initialize objects to a usable state. This is the job of a constructor in C++.

Writing our own init and dealloc methods requires a bit of work for things to work right. I'll present an example with both, and then explain the various parts and pieces.

#import <Foundation/Foundation.h>

@interface myObject: NSObject
{
@private
int count;
int refs;
}
-(id)init;
-(void)dealloc;
-(int)getCount;
-(void)incCount;
@end

@implementation myObject
-(id)init
{
self = [super init];
count = 0;
refs = 0;
printf("Running myObject's init method\n");
return self;
}

-(void)dealloc
{
printf("Running myObject's dealloc method\n");
[super dealloc];
}

-(int)getCount
{
return count;
}

-(void)incCount
{
++count;
}
@end

int main()
{
myObject *obj = [[myObject alloc] init];

printf("Object's count: %i\n",[obj getCount]);

[obj incCount];
[obj incCount];
[obj incCount];

printf("Object's count: %i\n",[obj getCount]);

[obj release];

return 0;
}


In this example, we have a simple class called myObject. This object implements a working init and dealloc, and little else.

First, an init method's declaration looks like this: -(id)init;. This means the return type is id, which is certainly not a standard C type. This type is similar to void* in C or C++. This basically means that we'll be returning a pointer to ourselves when the init method returns.

In the implementation of init we have some more magic. The first is self = [super init];. This line has two ideas; self and super. self is basically *this in C++. It's a reference to the object in question. So we're setting our object's reference to [super init]. This, unsurprisingly, uses super, another built-in variable that points to our class's parent class. Setting self to our object's parent's init method is required for proper inheritance.

Following all that stuff, init finishes with return self;. This returns a reference to our object so that the caller (main in this case) can access it.

In the dealloc method, we have a similar expression, [super dealloc];. This method does any necessary clean-up of our object using our class's parent's dealloc method. We are still responsible for any clean up we need to do ourselves (none in this example), but that's it.

Using init, dealloc, retain, and release allow us to start creating real complex classes, and do some basic memory management. There's still much more available in terms of memory management though, in the form of NSAutoReleasePools, but those are reserved for a future entry.

In case you were curious, the above program's output looks like this:
Running myObject's init method
Object's count: 0
Object's count: 3
Running myObject's dealloc method

Categories