Loading an image from CoreData to cellForRowAtIndexPath slows down scrolling

I am working on a UITableView, which is very similar to the native iOS app for photos: it has many lines with 4 thumbnails of images on each line. (i.e. each UITableViewCell has 4 UIImageViews). All thumbnails are loaded from the master data.

I reviewed my implementation several times, and I see performance improvements, but it still cannot scroll as smoothly as the Photos application.

I need to advise how to cache photos properly for better performance. This is what I tried:

1. My first attempt (extremely lag while scrolling)

  • Images are saved with the Transformable type in CoreData.
  • In the cellForRow function, each image is extracted from CoreData on a fly.

2. The second attempt (faster, but still slightly behind when scrolling)

  • Images are saved with binary data type with the option “external storage” marked in CoreData.
  • In the cellForRow function, each image is first loaded from Core Data and then stored in NSCache in memory, so the next time cellForRow is launched, we will use UIImage from NSCache directly if it is available.

After using NSCache to cache images loaded from CoreData, scrolling is noticeably faster, but since images still need to be loaded from CoreData when they are not yet available in NSCache, scrolling will still be distracted from time to time.

So, there should be a better way, I could preload all the images into memory, but since there may be a large number or lines of images, so I did not plan to preload the images at all.

What else can I do to load an image faster in cellForRowAtIndexPath?

+8
objective-c uitableview
source share
3 answers

In order for your scrolling to be smooth, no matter where your data comes from, you need to get the data in a separate stream and only update the user interface when you have data in memory. Grand Central Despatch is the way to go. Here's a skeleton that assumes you have a self.photos dictionary with a text link to the image file. Thumbnail images may or may not be loaded into the direct dictionary; may or may not be in the file system cache; otherwise issued from the online store. It can use Core Data, but the key to smooth scrolling is that you do not expect data from them, wherever they are.

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Photo Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; //identify the data you are after id photo = [self.photos objectAtIndex:indexPath.row]; // Configure the cell based on photo id dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ //move to an asynchronous thread to fetch your image data UIImage* thumbnail = //get thumbnail from photo id dictionary (fastest) if (!thumbnail) { //if it not in the dictionary thumbnail = //get it from the cache (slower) // update the dictionary if (!thumbnail) { //if it not in the cache thumbnail = //fetch it from the (online?) database (slowest) // update cache and dictionary } } } if (thumbnail) { dispatch_async(dispatch_get_main_queue(), ^{ //return to the main thread to update the UI if ([[tableView indexPathsForVisibleRows] containsObject:indexPath]) { //check that the relevant data is still required UITableViewCell * correctCell = [self.tableView cellForRowAtIndexPath:indexPath]; //get the correct cell (it might have changed) [[correctCell imageView] setImage:thumbnail]; [correctCell setNeedsLayout]; } }); } }); return cell; } 

If you use some kind of image storage manager in singleton mode, you expect the manager to deal with cache / database access information, which will simplify this example.

This part

  UIImage* thumbnail = //get thumbnail from photo id dictionary (fastest) if (!thumbnail) { //if it not in the dictionary thumbnail = //get it from the cache (slower) // update the dictionary if (!thumbnail) { //if it not in the cache thumbnail = //fetch it from the (online?) database (slowest) // update cache and dictionary } } 

will be replaced by something like

  UIImage* thumbnail = [[ImageManager singleton] getImage]; 

(you would not use a termination block, as you effectively provide it to the GCD when you return to the main queue)

+25
source share

I used the JMImageCache class to cache the image to disk, and then only saved the local URL (and the remote URL if you like) in CoreData. You can also create a thumbnail that you also save to disk and save the thumbnail URL along with the image URL in the master data and use a table thumbnail.

0
source share

Add a subclass of NSValueTransformer for the image in the master data,

The following code:

 + (BOOL)allowsReverseTransformation { return YES; } + (Class)transformedValueClass { return [NSData class]; } - (id)transformedValue:(id)value { return UIImageJPEGRepresentation(value, 0.5); } - (id)reverseTransformedValue:(id)value { return [[UIImage alloc] initWithData:value]; } 
0
source share

All Articles