0

I am having great success getting the look I want using a custom header view and the delegate method tableView: viewForHeaderInSection:. But I think it is producing a memory leak, and I'm not sure what to do about it.

The code is this:

- (UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
    NSLog (@"New header, section %d", section);
    ResultsHeaderView *header = [[ResultsHeaderView alloc] initWithFrame:CGRectMake(0, 0, defaultResultsHeaderSize.width, defaultResultsHeaderSize.height)];

    SearchResult *result = [[[[self.fetchedResultsController sections] objectAtIndex:section] objects] objectAtIndex:0];

    header.text = result.searchUsed.keywords;
    header.searchTermsEntity = result.searchUsed;
    header.resultDelegate = self;
    header.section = section;

    return [header autorelease];
}

As you can see, every time this is called, it instantiates a new object of type ResultsHeaderView, which is a subclass of UIView.

The problem is that it is called often, every time a section header is scrolled off of the view and then back on, it gets called. It gets called multiple times when a new section is added, even for the other sections (although I may have some control over that, and I'm going to look into it.)

I am wondering if there is something like tableView:dequeueReusableCellWithIdentifier: that can manage section header views, or a way to know when a section header view is in need of a release. I am not sure if the autorelease is sufficient to avoid a leak.

At the same time, my understanding is that creating cells is costly, and that's why they get reused with the dequeueReusableCellWithIdentifier process. I have to imagine this would be the same with section headers.

Would anyone who has come across this issue before comment?

Thomas Berger
  • 1,860
  • 13
  • 26
Jim
  • 5,940
  • 9
  • 44
  • 91

2 Answers2

2

Here is what I have decided to do, unless someone can see a flaw in it or can come up with a better idea.

In the view controller that manages the tableView, I added a property

NSMutableArray *viewsForSectionHeaders;

Then I have modified my tableView:viewForHeaderInSection:

- (UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {

    SearchResult *result = [[[[self.fetchedResultsController sections] objectAtIndex:section] objects] objectAtIndex:0];

    ResultsHeaderView *header;

    if (section < [viewsForSectionHeaders count]) {
        header = [viewsForSectionHeaders objectAtIndex:section];
    }
    else {
        header = [[[ResultsHeaderView alloc] initWithFrame:CGRectMake(0, 0, defaultResultsHeaderSize.width, defaultResultsHeaderSize.height)] autorelease];
        [viewsForSectionHeaders addObject:header];
    }

    header.text = result.searchUsed.keywords;
    header.searchTermsEntity = result.searchUsed;
    header.resultDelegate = self;
    header.section = section;

    return header;
}

In this case, I have an array of header views. It's not immediately obvious, but whenever the fetchedResultsController is updated, it may "mismatch" the previously associated headers with different sections than they were originally matched to. But this doesn't really matter because the header properties (text, searchTermsEntity, resultDelegate, and section) are all reset in the code above.

So, until I see something I missed, this looks like it serves the purpose of only instantiating header views when they are needed, and reusing those that have already been created.

(Note that I moved the autorelease inside where the view is allocated. I had an error where I was referring to a nil object, and this fixed it. Also, now that I think of it, havint the autorelease in the return statement would have repeated the autorelease on an object multiple times if it had been instantiated in a previous call to this code. I don't know if that is a real problem, but it was easy to straighten that out, while I was at it.)

Right now, I don't delete sections, so I don't have to worry about releasing header views. But if I did delete sections, then that means that a header view should be removed from the viewsForSectionHeaders array, too.

Jim
  • 5,940
  • 9
  • 44
  • 91
  • Looks like a sensible work around, and I haven't tried the code myself but reading over it the only comment I would make is that there appears to be an assumption made (and it may be a reasonable assumption) that the table view will always request the section headers in the correct order (i.e. it will never request section header 4 before 3 etc), if it ever requested them out of order (and I don't know that it ever would) I think your code might fail. Try scrolling the table view really fast to see if it's possible to "skip" a section and see if that causes problems. – Richard Baxter Aug 23 '11 at 21:09
1

Creating the view over and over probably is quite expensive CPU wise, though I wouldn't have thought it would leak (I have never noticed a leak when I've used that technique before).

An alternative solution (and one I've previously used myself) would be to create a different type of custom cell which can have a different cell identifier and which can be dequeued and reused as required - and then use this as the header, so row 0 of each section is effectively your section header and your genuine rows actually start at row 1.

Richard Baxter
  • 1,317
  • 1
  • 15
  • 24
  • Thanks, Richard. I have considered that method. I prefer to go with the section header in this case. – Jim Aug 20 '11 at 17:29
  • excellent workaround. I have found that a custom viewForHeaderInSection can cost me at least 5fps because the tableView calls it each time. It sucks. Apple should address this. But until then, your solution is great. – Jason Cragun Aug 23 '11 at 15:28
  • @Jason, thanks for the positive feedback, the only downside of this approach is that when using the plain style the header won't "float" at the top of the screen until the section has completely scrolled off the top - personally I don't like this anyway, but worth mentioning. – Richard Baxter Aug 23 '11 at 21:17