0

I am using custom cells and I call loadNibNamed:. This seems to cause a memory leak and I am not sure how to solve it. If I set the top level objects to nil afterwards, I still get the leak.

topLevelObjects = [[NSBundle mainBundle] loadNibNamed:@"customCell" owner:self options:nil];

I then do this

for (id currentObject in topLevelObjects){
        if ([currentObject isKindOfClass:[UITableViewCell class]]){
            cell =  (CustomCell *) currentObject;
            break;
        }
    }

and then mutate the properties on the cell.

The custom cell has a strong reference to a property, it is not a circular reference so I am not sure if this is the issue. What is the correct way to stop this abandoned memory when using ARC?

jszumski
  • 7,430
  • 11
  • 40
  • 53
some_id
  • 29,466
  • 62
  • 182
  • 304
  • Is the leak reported by Instruments? Do you get the leak when running on the device, or only in the simulator? Are you doing anything with the objects in the array? Are there IBOutlets? – Mike Weller Apr 19 '13 at 14:07
  • Edited my question thanks. Yes it is on the device, in instruments and I iterate the toplevelobjects to get the cell so that I can mutate its properties. – some_id Apr 19 '13 at 14:10

3 Answers3

1

I suspect that your leak may be coming from the outlets in the nib. Note this phrase in the docs on loadNibNamed::

To establish outlet connections, this method uses the setValue:forKey: method, which may cause the object in the outlet to be retained automatically.

In other words, loadNibNamed sometimes imposes an extra retain because of the odd way key-value-coding works.

However, that is speculation, and there's no need for it, because there's no need for you to call loadNibNamed: in the first place!

You're using a custom UITableViewCell subclass, designed in a nib? Then why not do this the normal way? Make a nib containing one top-level object: the cell. Design the cell in the nib, set its class, hook up its outlets etc. In your code, call registerNib:forCellReuseIdentifier: on the table view, to tell the table view about your nib. When you later call dequeueReusableCellWithIdentifier:, if there are no free cells in the reuse pile, the table view will load your nib and hand you cell. No muss, no fuss.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Thanks. I am seeing a cell in the debugger, but the tableview does not show any cells, but it seems to be scrollable with the scroll position indicator displaying too as if the cells are there, but nothing showing. – some_id Apr 19 '13 at 15:10
  • Hi there @Helium3. Do you need an example of how to design a cell in a nib for use in a table view? Here's a downloadable complete project ready for you to run in Xcode and study: https://github.com/mattneub/Programming-iOS-Book-Examples/tree/master/ch21p609addCellSubviewsNibOutlets – matt Apr 19 '13 at 15:22
  • Oh, and I should also mention: that code goes with the discussion from my book: http://www.apeth.com/iOSBook/ch21.html#_custom_cells That section tells you *all* the different ways to design a custom cell. The part about how to get the cell from a nib is the subsection called "Designing a cell in a nib". – matt Apr 19 '13 at 15:25
  • Thanks I will check the links. – some_id Apr 19 '13 at 16:28
  • I had to put the project on hold until tomorrow. Will return here once I have completed the task. :) – some_id Apr 25 '13 at 20:06
1

Matt's answer is spot on. Here's what the code looks like. Fixed my memory issue straight away as the cell is now loaded and dequeued as it should be.

First, register the nib

[self.tableView registerNib:[UINib nibWithNibName:@"customCell"
                                           bundle:nil] 
                           forCellReuseIdentifier:@"customCellID"];

Secondly...

CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:@"customCellID"];

The nib only needs a single tableviewcell in it and you're good to go!

Tim
  • 1,428
  • 19
  • 26
0

Consider using UINib instead. UINib caches the contents of the nib file in memory, saving trips to the file system. So for example, you could create an instance of nib and reference in a property:

self.cellNib = [UINib nibWithNibName:@"customCell" bundle:nil];

In the nib file, bind the cell to a property of your table view controller. Then whenever you need an instance of the cell, ask the UINib instance to instantiate it for you, and a reference to the new cell will automatically be stored in the property.

[self.cellNib instantiateWithOwner:self options:nil];
cell = self.customCell;
self.customCell = nil;

This approach should help avoid the memory management issues you experienced working with the top-level objects array, and also significantly improve performance.

jlehr
  • 15,557
  • 5
  • 43
  • 45
  • Thanks. I am unsure what self is in this scenario and what tableview property to bind to in the nib file? – some_id Apr 19 '13 at 18:30
  • 1
    This is certainly right for iOS 4 (replacing `loadNibNamed` which we had to use in iOS 3). But in iOS 5 and iOS 6, `registerNib:forCellReuseIdentifier:` is the way to go. – matt Apr 19 '13 at 19:24
  • @matt +1 True indeed, though UINib is handy for other cases, `registerNib:forCellReuseIdentifier:` works directly with the cell queueing mechanism, so it's an even better fit if available. Not sure how many developers still have to target iOS 4. – jlehr Apr 19 '13 at 19:32
  • @Helium3 `self` would be the table view controller, and it would be up to you to add a property to your controller class to bind to the cell in the nib file. – jlehr Apr 19 '13 at 19:35