viewForHeaderInSection creating a memory leak?

2020-08-01 06:49发布

问题:

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?

回答1:

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.



回答2:

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.