Greg Price Greg Price - 1 month ago 11
iOS Question

UITableView cell memory leak

I am having a leak in my tableview that I am populating with data.

Here is what is happening:

I have an

NSMutableArray
filled with
NSDictionaries
(contact information) that I load from sql database each time the user would select a new index letter, the array is filled with info for those contacts.

Since I am using an instance variable array to hold the data I figure something like this before each time new data is swapped in:

if (currentConnections != nil) {
[currentConnections release];
currentConnections = nil;
}


Would effectively get rid of my old data before I reset it.

However, this is where the strangeness begins. In my
tableView:cellForRowAtIndexPath:
I make calls like this:

NSString *firstname = [[currentConnections objectAtIndex:indexPath.row]
objectForKey:@"firstname"];
UILabel *name = [[UILabel alloc]
initWithFrame:CGRectMake(75, 12, 190, 15)];
name.font = [UIFont fontWithName:@"Gotham-Medium.ttf"
size:12];
name.textColor = [UIColor whiteColor];
name.backgroundColor = [UIColor clearColor];
name.text = firstname;


Where current connections is the array of dictionaries. I then add the
UILabel
as a subview of the cell:

[cell addSubview:name];
[name release];


The behavior from this is such:

When I run as is (releasing current connections before each swap then loading the new data to it) the app will crash for a bad access error.

When I do not release the subviews I add to the cell the app does not crash but I watch in horror as live bytes continue to accumulate in the memory allocations tool until the point when I switch contact info enough that I get a memory warning then crash.

It seems to me that there is some weird memory ownership issues going on with the cell and subviews. I do specify the cell as reusable and assign it to autorelease before loading each with data.

Any ideas of what may be causing this leak?

edit:

This is the current tableView:cellForRowAtIndexPath:

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

static NSString *MyIdentifier = @"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];

cell.accessoryType = UITableViewCellAccessoryNone;

if (cell == nil) {
NSLog(@"within");
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier] autorelease];
}

if (!ALOCATED) {


cellName = [[ UILabel alloc ]
initWithFrame:CGRectMake(75, 12, 190, 15)
];
cellName.font = [ UIFont
fontWithName:@"Gotham-Medium.ttf"
size:12
];
cellName.textColor = [ UIColor whiteColor ];
cellName.backgroundColor = [ UIColor clearColor ];

NSString *firstname = [[ currentConnections
objectAtIndex:indexPath.row
] objectForKey:@"firstname"
];

firstname = [ firstname stringByAppendingString:@" " ];

NSString *lastname = [[ currentConnections
objectAtIndex:indexPath.row
] objectForKey:@"lastname"
];

UIImage *profPic = [[ currentConnections
objectAtIndex:indexPath.row
] objectForKey:@"profilepicture"
];
connectionPhoto = [[ UIImageView alloc ] initWithFrame:CGRectMake(10, 8, 56, 56) ];
connectionPhoto.image = profPic;




NSString *fullname = [ firstname stringByAppendingString:lastname ];

cellName.text = fullname;


[ cell addSubview:cellName ];



cellTitle = [[ UILabel alloc ]
initWithFrame:CGRectMake(75, 31, 260, 13)
];
cellTitle.font = [ UIFont
fontWithName:@"ArialMT"
size:10
];
cellTitle.textColor = [ UIColor whiteColor ];
cellTitle.backgroundColor = [ UIColor clearColor ];
cellTitle.text = [[ currentConnections
objectAtIndex:indexPath.row
] objectForKey:@"title"
];


[ cell addSubview:cellTitle ];
[ cell addSubview:connectionPhoto ];
[ profPic release ];

ALOCATED = YES;

} else {



cellTitle.text = [[ currentConnections
objectAtIndex:indexPath.row
] objectForKey:@"title"
];

connectionPhoto.image = [[ currentConnections
objectAtIndex:indexPath.row
] objectForKey:@"profilepicture"
];


NSString *firstname = [[ currentConnections
objectAtIndex:indexPath.row
] objectForKey:@"firstname"
];

firstname = [ firstname stringByAppendingString:@" " ];

NSString *lastname = [[ currentConnections
objectAtIndex:indexPath.row
] objectForKey:@"lastname"
];
NSString *fullname = [ firstname stringByAppendingString:lastname ];

cellName.text = fullname;

}

return cell;

}


Where I am attempting to create one cell and reuse it, only changing the properties of its subviews each time.

EDIT

What was happening was that I was adding subviews to the cell object itself, which was causing a leak as these were not being deallocated. As soon as I started adding the views to the UITableViewCell member views aka cell.contentView, cell.accessoryView etc... the memory was released. So as daniel mentioned below, either create a custom table cell and add your views to your cell properties or add your views to the member uitablecellviews not the cell itself. The following worked for me:

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

static NSString *MyIdentifier = @"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];

cell.accessoryType = UITableViewCellAccessoryNone;

if (cell == nil) {

cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier] autorelease];
}




UILabel *cName = [[ UILabel alloc ]
initWithFrame:CGRectMake(105, 10, 190, 15)
];
cName.font = [ UIFont
fontWithName:@"Gotham-Medium.ttf"
size:12
];
cName.textColor = [ UIColor whiteColor ];
cName.backgroundColor = [ UIColor clearColor ];

NSString *firstname = [[ currentConnections
objectAtIndex:indexPath.row
] objectForKey:@"firstname"
];

firstname = [ firstname stringByAppendingString:@" " ];

NSString *lastname = [[ currentConnections
objectAtIndex:indexPath.row
] objectForKey:@"lastname"
];

NSString *fullname = [ firstname stringByAppendingString:lastname ];

cName.text = fullname;

[ cell.contentView addSubview:cName ];
[ cName release ];



cell.imageView.image = [[ currentConnections
objectAtIndex:indexPath.row
] objectForKey:@"profilepicture"
];



UILabel *cTitle = [[ UILabel alloc ]
initWithFrame:CGRectMake(105, 25, 190, 13)
];
cTitle.font = [ UIFont
fontWithName:@"ArialMT"
size:10
];
cTitle.textColor = [ UIColor whiteColor ];
cTitle.backgroundColor = [ UIColor clearColor ];
cTitle.text = [[ currentConnections
objectAtIndex:indexPath.row
] objectForKey:@"title"
];


[ cell.contentView addSubview:cTitle ];
[ cTitle release ];

return cell;

}

Answer

If you are reusing cells and everytime you setup a cell you are adding a UILabel to it then you can see how a lot of UILabels are being allocated that may cause your memory issue...If you want to add a label to the cell, you should only have to do this once when you allocate it, then if the cell is coming in as a reused cell, it would already contain the UILabel that you added, so you would only have to edit the labels text instead of adding a new UILabel... this is just a guess from what you have posted, some more code might clarify things...

The problem is apparent from the code you posted, you are leaking the cell instance variable, also keep in mind that cellForRowAtIndexPath gets called for every row in the table, so keeping one instance variable to keep track of something added to the cell will not work, the var will be keep being overwriten, but in your case you are leaking cells. You are adding cells to the UITableViewCell, never releasing them, and then on the next call to cellForRowAtIndexPath the cells var gets overwriten and the previous one leaks because you dont have a way to release it anymore...You can fix this by releasing cells...

Now you also have the issue of using one var to keep track of labels on cells, keep in mind that it's not one cell being allocated for reuse, it really is as many cells as can show on the screen, so keeping track of one var will not work is your issue...To solve that problem i would subclass UITableViewCell with a label property and have a method to reset the label text..

Hope this helps