May 31, 2009

Malloc Misunderstandings

I have been pulling my hair out lately with iPhone development memory management techniques in Objective-C, and I thought I'd share one of my recent understandings.


I had a nib-based view controller that I wanted to use as a tableHeaderView for my UITableViewController. I created the classes for my custom view (we'll call it CustomHeaderViewController) and imported them into my UITableViewController subclass. I want to manipulate the IBOutlets of the CustomHeaderViewController, so I wanted to keep a reference to the class. For this I'll auto-synthesize getters and setters for retaining.

Here's my CustomTableView.h at this point:

#import "CustomHeaderViewController.h"


@interface CustomTableView : UITableViewController {

CustomHeaderViewController *tableHeader;

}


@property (nonatomic, retain) CustomHeaderViewController *tableHeader;


In my implementation of the CustomTableView, I put in the proper @synthesize code and make sure I'm releasing tableHeader in my dealloc method. So far, so good (and so simple).

@implementation CustomTableView


@synthesize tableHeader;


- (void)viewDidLoad {

[super viewDidLoad];


// initialize my table header here.

}


- (void)dealloc {

[tableHeader release];

tableHeader = nil;

[super dealloc];

}


Here's where my mistake began. I know that the UITableView has a setTableHeaderView method, so I used it, directly allocating my new CustomHeaderViewController as I did it.


- (void)viewDidLoad {

[super viewDidLoad];


self.tableHeader = [[CompanyTableViewHeader alloc] init];

[self.tableView setTableHeaderView: tableHeader.view];

}


My project builds correctly, and everything seems to work fine, but there's a memory leak. To see this, let's look at the retain counts in my code now:


- (void)viewDidLoad {

[super viewDidLoad];


// [tableHeader retainCount] == 0


self.tableHeader = [[CustomHeaderViewController alloc] init];


// [tableHeader retainCount] == 2


[self.tableView setTableHeaderView: tableHeader.view];


// [tableHeader retainCount] == 2

}

- (void)dealloc {

// [tableHeader retainCount] == 2


[tableHeader release];


// [tableHeader retainCount] == 1


tableHeader = nil;

[super dealloc];

}


So when my UITableViewController is deallocated, the CustomHeaderViewController instance still has a retain count of 1. The problem lied in the line where I first allocated the instance (creating a retainCount of 1), and immediately assigned it to the to the tableHeader pointer (bumping the retainCount to 2). I can do two things to remedy this. First, I could change my tableHeader setter to only assign instead of retain:


@property (nonatomic, assign) CustomHeaderViewController *tableHeader;


The preferred way, however, would be to allocate the CustomHeaderViewController to a temporary pointer, then release it when done.


- (void)viewDidLoad {

[super viewDidLoad];


CustomHeaderViewController *header = [[CustomHeaderViewController alloc] init];

self.tableHeader = header;

[header release];

[self.tableView setTableHeaderView: tableHeader.view];

}


There you go. I hope this post saves someone the hour of debugging I've already lost!


P.S.: I apologize for the styles of the code snippets, I have never posted code on this blog before. If you have recommendations on how to improve formatting when copy-pasting from Xcode to the Blogger WYSIWYG editor, let me know!

1 comments:

Anonymous said...

Better yet, just don't use the DOT modifier when you allocate the initial object! The DOT modifier is what is causing it to bump it up to 2 on assignment, so just don't do it.