Five On Friday WWDC10
Apr 27

The UITableView class is a wonder of efficient memory management, if you use it correctly.

Here’s the standard template code that Xcode generates when you create a subclass of UITableViewController:

// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    static NSString *CellIdentifier = @"Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
    }

    // Set up the cell...

    return cell;
}

The keys here are the CellIdentifier variable and the call to dequeueReusableCellWithIdentifier, which enable the iPhone OS to reuse existing instances of UITableViewCell whenever possible.

(Don’t create a unique reuse identifier for each row as I’ve seen some developers do. Yes, it’s much easier to deal with asynchronous download of images for each row if you know how to uniquely identify the cell, and you know that the cell is still in memory. But that totally defeats the efficient memory management that UITableView is capable of.)

Under normal circumstances a UITableView will create one instance of a UITableViewCell per row that is visible on the screen. As you scroll, the cell instance that just rolled off the screen will be reused for the cell that is about to appear.

To verify that this memory management is working as it should, add a log statement each time a new cell is created:

if (cell == nil) {
    DLog(@"creating a new cell");
    cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}

When you run your app and start scrolling your table view, you should not see any creation of cells beyond the initial list (plus one). If you see “creating a new cell” log statements scrolling off the screen as you scroll the table view, you’ve got a problem.

If you just follow the standard Xcode template above, you should be fine. However if you’re loading a Nib for a custom table view cell layout using Apple’s recommended way, there’s an important detail you must not forget. (Tip of the hat to Jeff LaMarche for inspiring this blog post.)

Here’s the typical NIB loading code from Apple:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"CheckedTableViewCell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        DLog(@"creating a new cell");

        // Load the table view cell from a Nib file.
        [[NSBundle mainBundle] loadNibNamed:@"CheckedTableViewCell" owner:self options:nil];

        // The checkedTableViewCell property is just a temporary placeholder for loading the Nib.
        cell = checkedTableViewCell;

        // We don't need this anymore, so set to nil.
        self.checkedTableViewCell = nil;
    }

    return cell;
}

The key here is that the CellIdentifier value must also be entered into Interface Builder, like this:

UITableViewCell-Identifier.png

If you don’t do this, then UITableViewCells will not be reused. (A telltale sign of this is that you’ll see lots of “creating a new cell” log messages.) There is no compiler or runtime warning if you fail to enter this critical piece of information into Interface Builder. So that log statement can be a useful warning.

(BTW, if you’re wondering what DLog is, then see this post: The Evolution of a Replacement for NSLog.)

written by Nick \\ tags: , , , ,

4 Responses to “UITableView and Memory”

  1. Cory Says:

    “Yes, it’s much easier to deal with asynchronous download of images for each row if you know how to uniquely identify the cell”

    Do you have a good strategy for dealing with this?
    I’m only breaking the cell reuse code of conduct in one place in the app I’m currently working on. I think I’m ok, because there really really really will only ever be ~10 items in that table view. But I would be very interested how -you- would deal with async image downloads in a table view.

    Good post!

  2. Nick Says:

    @Cory: First I want to caution against relying on an assumption that there will only ever be ~10 items in a list, unless it’s based on something that is very unlikely to change, like the months of the year. Software requirements have a strange way of morphing in unexpected ways over time.

    I don’t have any code to post for the general case with larger lists with images. But markj has a good post with lots of improvements in the comments. There’s also sample code from Apple that deals with this issue.

  3. Patty Says:

    What if I want to make a table for use as a “configuration page”?
    (Like “settings”.)
    Each cell having a various object (switches, sliders, textfields, etc).

    I have 12 rows… and don’t really need to “create more” or “reuse” anything.

    How would that be done?

    (My current code works… except when I scroll… then my objects “forget” their settings.)

  4. Nick Says:

    @Patty: Your table view needs to be backed by a data model that is separate from the cells. As each cell needs to be rendered (in cellForRowAtIndexPath) you get the data to populate the cell. (This is the Model in the Model-View-Controller pattern.)

Leave a Reply