BuySellAds.com

Read the chapters in my new book Barcodes with iOS 7 as I hand them in. Great new app opportunities await!
Our DNA is written in Objective-C
Jump

Understanding Autoreleasing

Do you really understand when you need to retain and release some object? And what about autorelease? It took me around 3 months before I grasped when to use autorelease and now it’s second nature to me. But when I needed to explain to a coding buddy I was stumped. I found that my approach was one of feeling rather than knowledge.

So I decided it was time to do an experiment to visualize what happens to the retain count and when an instance is really released. So see what’s happening to our crash test dummy class I made a TestClass that overrides all the important methods so that we get an output to NSLog what is happening.

First I want to demonstrate how the retain counter – present in all Objective-C classes – is used to automatically determine when the instance can be freed from memory. Then I am going to show how to use a factory method which returns an autoreleased instance to make your life easier.

This class only reports when certain methods are called and it also has a property to keep its own name.

TestClass.h

#import 
 
@interface TestClass : NSObject
{
	NSString *myName;
}
 
@property (nonatomic, retain) NSString *myName;
 
- (id) initWithName:(NSString *)aName;
@end

TestClass.m

//
//  TestClass.m
//  autoreleased
//
//  Created by Oliver on 18.08.09.
//  Copyright 2009 Drobnik.com. All rights reserved.
//
 
#import "TestClass.h"
 
 
@implementation TestClass
 
@synthesize myName;
 
+ (id) alloc
{
	NSLog(@"TestClass alloc");
	TestClass *newInstance = [super alloc];
 
	NSLog(@"RetainCount now %d",  newInstance.retainCount);
 
	return newInstance;
}
 
- (id) initWithName:(NSString *)aName
{
	self.myName = aName;
 
	NSLog(@"TestClass init '%@'", myName);
 
	[super init];
 
	return self;
}
 
- (id) retain
{
	NSLog(@"TestClass retain '%@' to %d", myName, self.retainCount+1);
	return [super retain];
}
 
- (void) release
{
	NSLog(@"TestClass release '%@' from %d to %d", myName, self.retainCount, self.retainCount-1);
	[super release];
}
 
- (id) autorelease
{
	NSLog(@"TestClass autorelease '%@'", myName);
 
	return [super autorelease];
}
 
- (void) dealloc
{
	NSLog(@"TestClass dealloc '%@'", myName);
	[myName release];
	[super dealloc];
}
 
@end

So the first example is an example of a normal life cycle of alloc, init, using, release and dealloc.

// import for TestClass.h at the top
 
NSLog(@"Initialize variable");
TestClass *instance = [[TestClass alloc] initWithName:@"Fred"];
 
NSLog(@"Add to autoreleased array");
NSArray *anArray = [NSArray arrayWithObject:instance];
 
NSLog(@"Release locally");
[instance release];
 
NSLog(@"---- End of local code ---");

Checking the console output shows the following result:

Initialize variable
alloc
RetainCount now 1
init 'Fred'
Add to autoreleased array
retain 'Fred' to 2
Release locally
release 'Fred' from 2 to 1
---- End of local code ---
release 'Fred' from 1 to 0
dealloc 'Fred'

The first thing I learned from this is that the retain counter is set to 1 already inside alloc. After the local initialization it is still 1. Creating an autoreleased NSArray causes the counter to be incremented to 2. Then the local release causes decrement to 1 and then the local code ends. As soon as your local code is done control returns to the run loop which releases all objects which are marked to be autoreleased. In our case this is the NSArray. Before the memory for the array is freed it sends release to all contained objects and because release notices that the counter has dropped to 0 it also calls dealloc to free ‘Fred’.

This is the reason why you often see this pattern: alloc/init, add to array, release. This makes sure that the end of use of the local reference is reflected in the retain count. Because the array is still interested in the contained object staying “alive” it is responsible for the retain count 1. Would it be higher than 1 then the object would never get deallocated even when being removed from the array.

If you would over-release an instance then you might have still pointers to memory which already was freed. Accessing such areas via the pointer subsequently causes a memory exception which some people call a “Memory Protection Fault”. Those can be hard to spot because most of the time the exception does not occur in your code and thus the stack trace does not give you any clue.

Now for some autorelease magic. Let’s say we often want instances of TestClass named Fred. To make our lives easier we will create a so-called factory method. These methods usually are in the class for which they create a standard instance and can be recognized by the + as they are class methods. The have to be class methods because when you call them there is no instance yet to work with, in fact the goal of factory methods is to create such instances.

Add this code to TestClass and the appropriate line to the header.

+ (id) fredInstance
{
	TestClass *tmpInstance = [[[TestClass alloc] initWithName:@"Fred"] autorelease];
	return tmpInstance;
}

This creates a new instance by means of the usual alloc/init, but also marks it as autoreleased before returning it. This shortens the necessary code somewhat especially because you no longer need the release.

// import for TestClass.h at the top
 
NSLog(@"Initialize variable");
TestClass *instance = [TestClass fredInstance];
 
NSLog(@"Add to autoreleased array");
NSArray *anArray = [NSArray arrayWithObject:instance];
 
NSLog(@"---- End of local code ---");

Again inspecting the console output we see that the first release now also moved below the end of local code line.

Initialize variable
TestClass alloc
RetainCount now 1
TestClass init 'Fred'
TestClass autorelease 'Fred'
Add to autoreleased array
TestClass retain 'Fred' to 2
---- End of local code ---
TestClass release 'Fred' from 2 to 1
TestClass release 'Fred' from 1 to 0
TestClass dealloc 'Fred'

So, Fred is still not being leaked. We’re glad. For the final experiment I wanted to know what actually happens if you have an over-retained autoreleased instance. For this I modified the test code to retain the Fred instance several times and then I also intentionally leaked the array.

// import for TestClass.h at the top
 
NNSLog(@"Initialize variable");
TestClass *instance = [[[TestClass fredInstance] retain] retain];  // unnecessary retaining
 
NSLog(@"Add to leaked array");
NSArray *anArray = [[NSArray alloc] initWithObjects:instance, nil]; // intentional leak
 
NSLog(@"---- End of local code ---");

I had expected one of several things expected to happen but it turns out that autorelease works much more logicial than my wild dreams as we can see from the third console output.

Initialize variable
TestClass alloc
RetainCount now 1
TestClass init 'Fred'
TestClass autorelease 'Fred'
TestClass retain 'Fred' to 2
TestClass retain 'Fred' to 3
Add to leaked array
TestClass retain 'Fred' to 4
---- End of local code ---
TestClass release 'Fred' from 4 to 3

When control returns to the run loop after our local code there is only a single call to the TestClass release method. That’s the autorelease process debunked: it’s basically just a single release call done by the OS for you. The final retain count is 3 because there where two explicit retains and one implicit retain from adding to the leaked array.

Autoreleasing is a convenient way to reduce some code and make it easier readable as a result. However it is no magic bullet that will forcefully dealloc your instances. If you have too high a retain count from unnecessarily retaining instances your instances will leak even with autorelease. Autorelease simply does what its name suggest: a single automatic call to the release method as soon as your code gives control back to the run loop.

Because it is so convenient most of the methods in the SDK return autoreleased instances. One famous example is stringWithFormat of the NSString class. The nomenclature is that if the method name is prefixed with new, init or copy then it is not autoreleased. In all other cases it has to be.

Regardless if you are creating a local method, a class category or a factory method most likely the correct answer is to return an autoreleased object. The only reason I found so far to not to autorelease was if I am writing custom initWith… methods which allow me to pass parameters to my custom initializer.


Categories: Recipes

%d bloggers like this: