views:

527

answers:

3

Hi,

I'm working on a iPhone app which has a pretty large UITableView with data taken from the web, so I'm trying to optimize its creation and usage.

I found out that dequeueReusableCellWithIdentifier is pretty useful, but after seeing many source codes using this, I'm wondering if the usage I make of this function is the good one.

Here is what people usually do:

UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];

if (cell == nil) {
  cell = [[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"Cell"];

// Add elements to the cell
return cell;

And here is the way I did it:

NSString identifier = [NSString stringWithFormat:@"Cell %d", indexPath.row]: // The cell row

UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:identifier];

if (cell != nil)
  return cell;

cell = [[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:identifier];
// Add elements to the cell
return cell;

The difference is that people use the same identifier for every cell, so dequeuing one only avoids to alloc a new one.

For me, the point of queuing was to give each cell a unique identifier, so when the app asks for a cell it already displayed, neither allocation nor element adding have to be done.

In fine I don't know which is best, the "common" method ceils the table's memory usage to the exact number of cells it display, whislt the method I use seems to favor speed as it keeps all calculated cells, but can cause large memory consumption (unless there's an inner limit to the queue).

Am I wrong to use it this way? Or is it just up to the developper, depending on his needs?

+1  A: 

I Think the first one ist the best (and as you said common) way to implement a UITableView. With your second way there will be memory allocated for every new cell which is displayed and no memory will be reused.

AlexVogel
Well memory will be reused if the user scrolls down and then scrolls back up... Or anytime the tableView accesses a cell which was displayed once then hidden.
Jukurrpa
Sure, but there will be a memory footprint for every cell that was ever displayed and not only for the 4-5 cell that are currently displayed.I've had a similar problem with annotations for the map. Switching to an constant identifier brought a noticeable increase of performance.
AlexVogel
Then I guess it's up to me... Would it be better to cache any data I use to create the cell rather than the cell itself?
Jukurrpa
+2  A: 

The purpose of dequeueReusableCellWithIdentifier is to use less memory. If the screen can fit 4 or 5 table cells, then with reuse you only need to have 4 or 5 table cells allocated in memory even if the table has 1000 entries.

In the second way there is no reuse. There is no advantage in the second way over just using an array of table cells. If your table has 1000 entries then you will have 1000 cells allocated in memory. If you are going to do that you would put them in an array and just index the array with the row number and return the cell. For small tables with fixed cells that may be an reasonable solution, for dynamic or large tables it is not a good idea.

progrmr
You're right about the fact that with my method an array could do the job.Would ~100 cells represent a 'too' large amount of memory allocated at once?
Jukurrpa
Well, I changed for the common method (which is complicated when a row has many subviews), and besides lower memory consumption, scrolling overall seems smoother, I'm not sure why.Thanks for the advice anyways!
Jukurrpa
A: 

As for cell identifier- Instead of just using "cell" for the identifier, and instead of using a unique identifier like the OP, could you use a "type-identifier"? For example, if my table had 3 types of cells- one with a very complicated sub-layout, one with just Style1, and one with Style2, should I identify those three all separately and then just rebuild them?

For example:

-(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath{

NSString ident = @"";
if(indexPath.section == 0) ident= @"complicated";
if(indexPath.section == 1) ident= @"style1";
if(indexPath.section == 2) ident = @"style2";

UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:ident];


if(cell == nil){


   if(ident == @"complicated"){
      cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:ident] autorelease]; 
     // do excessive subview building
   }
   if(ident == @"style1"){
      cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyle1 reuseIdentifier:ident] autorelease]; 
   }

   if(ident == @"style2"){
      cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyle2 reuseIdentifier:ident] autorelease]; 
   }


}
if(ident == @"complicated"){
   // change the text/etc (unique values) of our many subviews
}
if(ident == @"style1"){
  [[cell textLabel] setText:@"Whatever"];
}
if(ident == @"style2"){
  [[cell textLabel] setText:@"Whateverelse"];
}

return cell; 

}

now this code probably won't run because i wrote it here but hopefully you get the idea.

I don't think Apple would have created the whole reusable cell idea with identifiers if they wanted all the identifiers to be "cell", don't you think?

Tim
In your case I'd use an identifier for each cell layout. Which means one for your "complicated" cells, one for the "style1" cells and maybe a third for style2 cells, if their subviews differs from style1 ones.In the case dequeue returns nil, add the cell's subview with tags (defined in an enum or something), and then initialize them. In the case dequeue returns a cell, simply retrieve subviews using tags and change them.Separating your code in one method for each section would be nice too :)
Jukurrpa