Memory leak in UITableView when scrolled on iOS 5.1 - ios

For each time UITableview is scrolled, there is a memory leak of 48 bytes.
Responsible library : libsystem_c.dylib
Responsible frame : strdup.
This is observed only on iOS 5.1 and not on earlier versions.
Did anyone else faced the same? Is this a bug in iOS 5.1?
Code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath(NSIndexPath *)indexPath
{
NSString *cellIdentifier = [[NSString alloc] initWithString:#"fontSelectionCell"];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
[cellIdentifier release];
if (cell == nil)
{
cell = [[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];
}
cell.textLabel.text = [fontNameList objectAtIndex:[indexPath row]];
cell.selectionStyle =UITableViewCellSelectionStyleGray;
cell.textLabel.font = [UIFont systemFontOfSize:17.0];
if ([fontName isEqualToString:cell.textLabel.text])
{
cell.accessoryType = UITableViewCellAccessoryCheckmark;
cell.textLabel.textColor = [UIColor blueColor];
}
else
{
cell.accessoryType = UITableViewCellAccessoryNone;
cell.textLabel.textColor = [UIColor blackColor];
}
return cell;
}

It could be due to the way you are handling the cell identifier. I'm actually surprised it does not crash for you, since you release cellIndentifier but then reference it when creating a new cell (i.e. when a cell wasn't return for reuse from dequeueReusableCellWithIdentifier).
The standard/accepted way to use a cell identifier is to use a static (because it won't ever change, and it will only be alloc-ed once and not potentially 100s of times since cellForRowAtIndexPath is called constantly when scrolling a table). This would make your code much more efficient.
i.e.
- (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"fontSelectionCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];
}
...
}
Could you try changing cellIdentifier and see if you still get the leak?

I think you are having this issue that was already report on iOS 5.1. I am having that myself too. At the moment I wasn't able to find the link in apple's forums concerning this issue.

Related

UITableViewCell Identifier without ARC floods memory

I'm working on an old project that runs without ARC. It has a lot of bugs and the code looks ugly and i'm rewriting it.
Take a quick look at my code
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [self.table dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
cell = [self createCellWithInfo:[self.search objectAtIndex:indexPath.row]];
return cell;
}
-(UITableViewCell *)createCellWithInfo:(NSDictionary *)info{
UITableViewCell * cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#“Cell”] autorelease];
//set image for cell
//set text for cell.textlabel
//set text for cell.detailTextLabel
//create an UIButton and add to cell.content view
return cell;
}
the point is at this line of code
[[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#“Cell”] autorelease]
if I use #"Cell" here, then the memory will rise up when I'm scrolling up and down continously on the table.
After about 15 seconds of scrolling, my iphone 5c becomes lag.
if I set it to nil, everything is fine.
Can anybody explain this please ? I'm not familliar with non-ARC.
Thanks.
Inside the if block you are creating the cell without calling autorelease, which leaks memory without ARC.
And after the if block you are recreating it anyway (whether or not it was recycled), with autorelease this time, where all you should really be doing is reset its relevant properties so that you can successfully reuse a recycled cell (or configure a new cell).
Try replacing your code as follows:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [self.table dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
}
[self updateCell:cell withInfo:[self.search objectAtIndex:indexPath.row]];
return cell;
}
-(void)updateCell:(UITableViewCell *)cell withInfo:(NSDictionary *)info{
//set image for cell
//set text for cell.textlabel
//set text for cell.detailTextLabel
//create an UIButton and add to cell.content view
}
UITableViewCell *cell = [self.table dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil){
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
alone takes care of cell initialisation, you don't need the other line.

UITableViewCell subtitle. Not getting it to work

Using Xcode 4.6, I am trying to display a typical UITableView with its cells with subtitles.
If I am not wrong, the code is this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
Test *myTest = [self listTests][indexPath.row];
cell.textLabel.text = [myTest name];
UIFont *cellFont = [UIFont systemFontOfSize:16.0];
cell.textLabel.font = cellFont;
UIFont *detailFont = [UIFont systemFontOfSize:12.0];
NSMutableString *detailText = [NSMutableString stringWithFormat:#"%d", [myTest numQuestions]];
[detailText appendString:#" preguntas."];
cell.detailTextLabel.text = detailText;
cell.detailTextLabel.font = detailFont;
return cell;
}
For some reason, it never passes through this line of code:
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
so the cell is never initialized with UITableViewCellStyleSubtitle.
It is somehow getting ALWAYS FROM THE BEGINING a valid cell when doing [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
What can I be doing wrong?
It is really weard. I always use this code and it normally works. What else can I be doing wrong somewhere else?
This happens when your cell is defined using a storyboard prototype. In this case the reusable cells are pre-created using the initWithCoder: method, so if (cell == nil) never gets hit. See this question for more information.
Since it appears that you would like to use a cell with a standard style, changing the table to not use a storyboard prototype or setting the prototype to "Subtitle" should fix this problem.

iOS tableView cell caching issues not loading every cell

I'm having a bit of difficulty understanding what is going wrong and how to fix this.
-(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];
NSUInteger row = [indexPath row];
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
cell.selectionStyle = UITableViewCellSelectionStyleBlue;
UILabel *where = [[UILabel alloc] initWithFrame:CGRectMake(88.0, 0.0, 155.0, 22.0)];
where.text = [delegate.destinationArray1 objectAtIndex:row];
where.font = [UIFont fontWithName:#"Helvetica" size:12.0];
where.textColor = [UIColor blueColor];
[cell.contentView addSubview:where];
return cell;
}
This doesn't work properly but this does:
-(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];
}
NSUInteger row = [indexPath row];
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
cell.selectionStyle = UITableViewCellSelectionStyleBlue;
UILabel *where = [[UILabel alloc] initWithFrame:CGRectMake(88.0, 0.0, 155.0, 22.0)];
where.text = [delegate.destinationArray1 objectAtIndex:row];
where.font = [UIFont fontWithName:#"Helvetica" size:12.0];
where.textColor = [UIColor blueColor];
[cell.contentView addSubview:where];
return cell;
}
They both get populated by "delegate.destinationArray1" but when all the code is inside the curly braces of
if(cell == nil)
the list gets unordered and repeats cells and misses some out. I can't use the latter way as it creates a MASSIVE memory leak when scrolling.
Any ideas?
I did the exact same thing when I started using UITableViews. The reason for the memory leak is that in the second implementation (the one that works) you are actually creating every cell, every single time. Let me try to explain a bit more.
You never want to set content of a cell between the (cell == nil). The reason for this is the reuseIdentifier. If the table needs to display a new cell it will grab one and see if it has already been alloced/inited. If it has it will just use it. If that is the case the content will already be set in the cell you grabbed and you are not telling it to set it any differently.
between the (cell == nil) only create and establish the view. Not the content. All content should be set after. So then no matter what cell it grabs it can always set the content. So this is what you want:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(!cell) // or (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
cell.selectionStyle = UITableViewCellSelectionStyleBlue;
UILabel *where = [[UILabel alloc] initWithFrame:CGRectMake(88.0, 0.0, 155.0, 22.0)];
where.font = [UIFont fontWithName:#"Helvetica" size:12.0];
where.textColor = [UIColor blueColor];
where.tag = 1;
[cell addSubview:where];
}
NSUInteger row = indexPath.row;
UILabel *where = [cell viewWithTag:1];
where.text = [delegate.destinationArray1 objectAtIndex:row];
return cell;
}
I just coded this in StackoverFlow so sorry if there are any small syntax errors.
The cell object is reused or created by the first statement. After checking cell for nil and creating a cell, you must not create another cell.
So delete the line
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
which comes after
NSUInteger row = [indexPath row];
and you'll work with the correct cell object.
When the tableview reuses cells it is based on the CellIdentifier. The tableview doesn't care what attributes you've set on the cell. In the first case the reuse it happening and it recognizes a cell it can use but that cell has the wrong information on it.
What I do is subclass UITableViewCell and do all the work inside of that class. Here is a quick snippet
#implementation AlertCell
//Custom init method
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier withHeight:(float)height {
//Whatever you need to do
}
//Place the views
- (void)layoutSubviews {
}
//Custom Setter method
- (void)setAlert:(CWAlert *)incomingAlert withAssets:(NSDictionary *)assets {
}
#end
Then you do something like this
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier;
CWAlertCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
cell = [[AlertCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier withHeight:[self convertAssetsLengthToCellHeight:assetsLength]];
UIView *selectedView = [[UIView alloc] init];
selectedView.backgroundColor = [UIColor colorWithHexString:#"F6F6F6"];
cell.selectedBackgroundView = selectedView;
}
NSDictionary *alertInfo = [AlertCell getNeededCellAssets:alert];
[cell setAlert:alert withAssets:alertInfo];
return cell;
}
I can show more code from the subclass if needed.

Single UITableViewCell with AccessoryView

I'm trying to build an UITableView with some long texts inside each cell. Cells are without AccessoryView, except for one cell (the 8th one), that is a sort of button to open a detail view.
Consider this code:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
CGSize size = [[quotes objectAtIndex:indexPath.row] sizeWithFont:16 constrainedToSize:CGSizeMake(self.view.frame.size.width, CGFLOAT_MAX)];
return size.height+20;
}
- (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];
}
cell.textLabel.lineBreakMode = UILineBreakModeWordWrap;
cell.textLabel.numberOfLines = 0;
cell.textLabel.text = [quotes objectAtIndex:indexPath.row];
if(indexPath.row==7){
[cell setAccessoryType:UITableViewCellAccessoryDetailDisclosureButton];
}
return cell;
}
It works, but the problem is that when I scroll to the bottom of the Table (the 8th is also the last row) and then I go back to the upper side, another AccessoryView is added to a random point (more or less the 3rd cell, but I don't know if it is inside of it or is floating around randomly).
Is it something related to cell reusing by iOS? How can I avoid it?
Thanks in advance.
You have to explicitly set no disclosure button to every cell but the one that you wish to have disclosure. This way when the cell gets reused elsewhere its disclosure indicator is removed:
if(indexPath.row==7){
[cell setAccessoryType:UITableViewCellAccessoryDetailDisclosureButton];
}else{
[cell setAccessoryType:UITableViewCellAccessoryNone];
}
The cell is being reused (as demonstrated by your call to -dequeueReusableCellWithIdentifer).
The answer is to set the cell to wanted defaults after it's been dequeued, or add an else clause to the if statement to handle it.
- (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];
}
cell.textLabel.lineBreakMode = UILineBreakModeWordWrap;
cell.textLabel.numberOfLines = 0;
cell.textLabel.text = [quotes objectAtIndex:indexPath.row];
// Set to expected default
[cell setAccessoryType:UITableViewCellAccessoryNone];
if(indexPath.row==7){
[cell setAccessoryType:UITableViewCellAccessoryDetailDisclosureButton];
}
return cell;
}
This is due to cell reuse as you surmise. You must explicitly set UITableViewCellAccessoryNone for cells at index paths other than 7.

Caching issues with TDBadgedCell

This post is closely related to my previous post: TDBadgedCell keeps caching the BadgeNumber
The "badge" from TDBadgedCell keeps caching the numbers. A very simple example is shown here:
- (UITableViewCell *)tableView:(UITableView *)_tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
TDBadgedCell *cell = (TDBadgedCell *)[_tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[TDBadgedCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
[cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
}
[cell setBadgeColor:[UIColor blackColor]];
[cell setBadgeNumber:[indexPath row]];
[[cell textLabel] setText:[NSString stringWithFormat:%#"%d", [indexPath row]]];
return cell;
}
Anyone has any clue why this happens? The textLabel and detailTextLabel don't cache the data. Any additonal info would be welcome as well, as I seem to have a lot of issues with the caching of graphics in UITableViewCells. Any best practices or other useful information would be most welcome.
OK, I figured this one out. Apparently I shouldn't use the default code to initialize my cell when using the TDBadgedCell. The following code:
TDBadgedCell *cell = (TDBadgedCell *)[_tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[TDBadgedCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
[cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
}
Needs to be changed into this:
TDBadgedCell *cell = [[[TDBadgedCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
I am wondering if this is clean in terms of memory usage and such, but it'll do for now at least.
I thought this was fixed in commit 2d255f075fe53ad10afe8eb65666207a8f2c65d0, which was made on March 22, 2013. In my case, this initially seemed to fix the issue most of the time, but I still saw cached badges occasionally. Then I realized that you can fix this once-and-for-all by using two different cells: when you need a badged cell, dequeue a TDBadgedCell, and when you don't need a badge, dequeue an ordinary UITableViewCell.

Resources