Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scrolling performance and UIImage drawing

I'm building a UITableView similar to iPod.app's album browsing view:

IMG_2316.PNG

I'm importing all the artists and album artworks from the iPod library on first launch. Saving everything to CoreData and getting it back into an NSFetchedResultsController. I'm reusing cell identifiers and in my cellForRowAtIndexPath: method I have this code:

Artist *artist = [fetchedResultsController objectAtIndexPath:indexPath];
NSString *identifier = @"bigCell";

SWArtistViewCell *cell = (SWArtistViewCell*)[tableView dequeueReusableCellWithIdentifier:identifier];

if (cell == nil)
    cell = [[[SWArtistViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier] autorelease];

cell.artistName = artist.artist_name;
cell.artworkImage = [UIImage imageWithData:artist.image];

[cell setNeedsDisplay];

return cell;

My SWArtistViewCell cell implements the drawRect: method to draw both the string and image:

[artworkImage drawInRect:CGRectMake(0,1,44,44)]
[artistName drawAtPoint:CGPointMake(54, 13) forWidth:200 withFont:[UIFont boldSystemFontOfSize:20] lineBreakMode:UILineBreakModeClip];

Scrolling is still choppy and I just can't figure out why. Apps like iPod and Twitter have butter smooth scrolling and yet they both draw some small image in the cell as I do.

All my views are opaque. What am I missing?

EDIT: here's what Shark says:

alt text

I'm not familiar with Shark. Any pointer as of what are these symbols related to? When I look at the trace of these, they all point to my drawRect: method, specifically the UIImage drawing.

alt text

Would it point to something else if the chokehold was the file reading? Is it definitely the drawing?

EDIT: retaining the image

I've done as pothibo suggested and added an artworkImage method to my Artist class that retains the image created with imageWithData:

- (UIImage*)artworkImage {
    if(artworkImage == nil)
        artworkImage = [[UIImage imageWithData:self.image] retain];

    return artworkImage;
}

So now I can directly set the retained image to my TableViewCell as follow:

cell.artworkImage = artist.artworkImage;

I also set my setNeedsDisplay inside the setArtworkImage: method of my tableViewCell class. Scrolling is still laggy and Shark shows exactly the same results.

like image 683
samvermette Avatar asked Dec 17 '22 20:12

samvermette


2 Answers

Your profiling data strongly suggests that the bottleneck is in the unpacking of your PNG images. My guess is that 58.5 % of your presented CPU time is spent unpacking PNG data (i.e. if the memcpy call is also included in the loading). Probably even more of the time is spent there, but hard to say without more data. My suggestions:

  1. As stated before, keep loaded images in UIImage, not in NSData. This way you won't have to PNG-unpack every time you display an image.
  2. Put the loading of your images in a worker thread, to not affect the responsiveness of the main thread (as much). Creating a worker is real easy:

    [self performSelectorOnMainThread:@selector(preloadThreadEntry:) withObject:nil waitUntilDone:NO];

  3. Preload images far ahead, like 100 rows or more (like 70 in the direction you're scrolling, keep 30 in the opposite direction). If all your images need to be 88x88 pixels on retina, 100 images would require no more than two MB.

  4. When you profile more the calls to stuff named "png", "gz", "inflate" and so forth might not go way down your list, but they will certainly not affect the feeling of the application in such a bad way.

Only if you still have performance problems after this, I would recommend you look into scaling, and for instance loading "[email protected]" images for retina. Good luck!

like image 84
Jonas Byström Avatar answered Jan 14 '23 05:01

Jonas Byström


[UIImage imageWithData:] doesn't cache.

This means that CoreGraphic uncompress and process your image every single time you pass in that dataSource method.

I would change your artist's object to hold on a UIImage instead of NSData. You can always flush the image on memoryWarning if you get them a lot.

Also, I would not recommend using setNeedsDisplay inside the dataSource call, I would use that from within your cell.

SetNeedsDisplay is not a direct call to drawRect:

It only tells the os to draw the UIVIew again at the end of the runloop. You can call setNeedsDisplay 100 times in the same runloop and the OS will only call your drawRect method once.

like image 42
Pier-Olivier Thibault Avatar answered Jan 14 '23 05:01

Pier-Olivier Thibault