It seems that after a little use my app becomes slow and unresponsive on the UITableViewController. There are some pretty intensive methods but once these are done there’s no reason why they should effect the overall performance of the app.
I’ve been using instruments to determine what it could be, but it’s proving very inconclusive. First off I can’t recreate the issue via one method, it just seems to occur from general usage. The main thing using CPU is my cellForRowAtIndexPath according to thread usage. While I do actually do quite a lot of calculations, it doesn’t explain two things. 1) Why this issue develops over time. I can scroll up and down the TableViewController several times when I first boot up the app and everything is super smooth. 2) Even when I remove all but 3 or 4 cells the unresponsiveness remains.
Another observation is that a modal ViewController has a very smooth animation when the app is first loaded, but ends up having this horrible jagged animation, sometimes only 1 or 2 frames, later on. Again, there are a couple of fairly complex calculations on the dismissal of this Modal (including a managedObjectContext save), and it does close to reveal the UITableView beneath (meaning some cellForRowAtIndexPath) but there is no way these two things alone can bring the animation to virtually 0 fps. All this has lead me to believe that resources are getting used up and are not returning. Now sadly, I don’t know enough about the iOS environment to be sure about the next assumptions, but here goes:
-
Memory. Simply put, it can’t really be this can it? Even though my app is very memory intensive with lots of images I’m pretty sure iOS removes things from the RAM as it begins to run out. On top of that the diagnostics show that I have had 50MB free when experiencing a huge amount of lag in the app.
-
CPU. I thought potentially that something was using up all my CPU like the last time I had performance issues (I had called an infinite loop of a method, whoops) however, the app seems to use CPU only when needed. In other words, it goes back to 0 usage when the app is idle. Just to explain how this is significant; if the CPU, as a resource, was being eaten up by something then it would explain why
cellForRowAtIndexPathwas struggling over time. However, since it’s not getting eaten up there’s no reason why prolonged usage of the app should causecellForRowAtIndexPathto use too much of the CPU. Therefore I can’t see how my issue is caused by the CPU being hogged.
Naturally as those are the only two resources I can think of I am completely and utterly stumped. I have no idea why the app should become slow over time. In effect, I can’t see how it can be code as the app works fine when first starting up and I can’t see how it can be the resources when there seems to be plenty left.
Since I’m new to Objective-C/iOS development I suspect that there’s something I’m missing. If you could help me identify it, I’d really appreciate it.
UPDATE: Posting my code of cellForRowAtIndexPath and willDisplayCell:
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
myEntityForConfigureCell = [self.fetchedResultsController objectAtIndexPath:indexPath];
searchResultForConfigureCell = [searchResults objectForKey:myEntityForConfigureCell.trackId];
// Example of how I fill in cell information. There are far more than shown.
nameLabelForConfigureCell = (UILabel *)[cell viewWithTag:1001];
nameLabelForConfigureCell.text = myEntityForConfigureCell.name;
genreLabelForConfigureCell = (UILabel *)[cell viewWithTag:1002];
genreLabelForConfigureCell.text = searchResultForConfigureCell.genre;
// Example of how I do the ImageViews using AFNetworking
imageForConfigureCell = (UIImageView *)[cell viewWithTag:1000];
[imageForConfigureCell setImageWithURL:[NSURL URLWithString: searchResultForConfigureCell.artworkURL60]];
imageForConfigureCell.layer.cornerRadius = 9.4;
imageForConfigureCell.layer.masksToBounds = YES;
// The RateView for showing a start rating
rateViewForConfigureCell = [[DYRateView alloc] initWithFrame:CGRectMake(73, 44, 75, 12) fullStar:[UIImage imageNamed:@"StarFullSmall.png"] emptyStar:[UIImage imageNamed:@"StarEmptySmall.png"]];
rateViewForConfigureCell.rate = [searchResultForConfigureCell.rating floatValue];
rateViewForConfigureCell.alignment = RateViewAlignmentLeft;
[cell.contentView addSubview:rateViewForConfigureCell];
// Setting Sections for all Core Data entries only if BOOL isLoading is YES, but **has** finished downloading informaton, i.e. just before completion.
if ([searchResults count] == [fetchedResultsController.fetchedObjects count]) {
if (isLoading) {
NSLog(@"Is loading so Setting Sections");
isLoading = NO;
[self cycleThroughEntriesAndSetSection];
}
else if (!isLoading){
[loadingHudView removeFromSuperview];
}
}
}
And now for willDisplayCell:
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
myEntityForDisplayCell = [self.fetchedResultsController objectAtIndexPath:indexPath];
searchResultForDisplayCell = [searchResults objectForKey:myEntityForDisplayCell.trackId];
UILabel *currentPrice = (UILabel *)[cell viewWithTag:1003];
if ([myEntityForDisplayCell.price1 floatValue] == [searchResultForDisplayCell.price2 floatValue]) {
currentPrice.textColor = [UIColor blackColor];
}
else if ([myEntityForDisplayCell.price1 floatValue] > [searchResultForDisplayCell.price2 floatValue])
{
currentPrice.textColor = [UIColor colorWithRed:0 green:0.9 blue:0 alpha:1];
}
else if ([myEntityForDisplayCell.price1 floatValue] < [searchResultForDisplayCell.price2 floatValue])
{
currentPrice.textColor = [UIColor redColor];
}
cell.backgroundColor = nil;
if (isLoading) {
loadingHudView.numOne = [searchResults count];
loadingHudView.numTwo = [fetchedResultsController.fetchedObjects count];
[loadingHudView setNeedsDisplay];
}
if ([searchResults count] == [fetchedResultsController.fetchedObjects count]) {
[loadingHudView removeFromSuperview];
if ([myEntityForDisplayCell.price1 floatValue] < [searchResultForDisplayCell.price2 floatValue]) {
cell.backgroundColor = nil;
}
else
{
cell.backgroundColor = [UIColor colorWithRed:1 green:0.85 blue:0 alpha:0.45];
}
}
}
UPDATE 2: Found something with the Analyser tool. I’m not entirely sure how it’s a leak, but here goes anyway.
SearchResult *searchResult = [[SearchResult alloc] init];
for (id i in fetchedResultsController.fetchedObjects) {
MyEntity *myEntity = i;
searchResult = [searchResults objectForKey:myEntity.id];
This bit of code will probably be executed once per everytime a new entity is added to the Core Data database, i.e. not particularly often.
Three observations:
You say “lots of images I’m pretty sure iOS removes things from the RAM as it begins to run out” — FYI, if you’re using
imageNamed, it’s not very good about managing your memory. When I’m doing image caching, I use my ownNSCache. This is especially important if you’re using large images.See WWDC 2012 – Building Concurrent User Interfaces on iOS for practical example of how to use Instruments to identify where your performance bottlenecks are.
Finally, see Finding leaks with Instruments for guidance on how to use Instruments to find leaks. Also, don’t overlook the static analyzer, which is important if you don’t use ARC, or if you use any Core Foundation calls.
Other than that, it’s hard to identify sources of your problems without seeing code.
Update:
Now that you’ve provided a little source code, the one thing that leaps out at me is the
addSubviewofrateViewForConfigureCell. Given that cells are reused, you’ll be adding that repeatedly to cells. So, I’d suggest replacing:With:
Either do that, or add the
DYRateViewto your cell prototype.