View Async Scrolling Table

I upload an image to a table view cell, each cell has an image. I am adapting a couple of guides to the code below, but I'm still slowing down.

I am loading these images from a document catalog. Any tips or ideas on how to speed up this process?

Change modified code:

Beer *beer = (Beer *) [self.fetchedResultsController objectAtIndexPath:indexPath]; cell.displayBeerName.text = beer.name; // did we already cache a copy of the image? if (beer.image != nil) { // good. use it. this will run quick and this will run most of the time cell.beerImage.image = beer.image; } else { // it must be the first time we've scrolled by this beer. do the expensive // image init off the main thread cell.beerImage.image = nil; // set a default value here. nil is good enough for now [self loadImageForBeer:beer atIndexPath:indexPath]; } - (void)loadImageForBeer:(Beer *)beer atIndexPath:(NSIndexPath *)indexPath { dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); dispatch_async(queue, ^{ UIImage *image = [UIImage imageWithContentsOfFile:beer.imagePath]; beer.image = image; dispatch_sync(dispatch_get_main_queue(), ^{ UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath]; cell.beerImage.image = image; }); }); } 
+6
source share
4 answers

Your algorithm looks pretty good. You have escaped many typical traps. If you still have UI performance issues, I would suggest a couple of things:

  • You should try to cache images in memory. You can use NSMutableArray or NSMutableDictionary , but in the best way to cache images in an ios application? Caleb discusses the virtues of the NSCache class, which makes processing easier. If you make cache images, make sure that you are responding to pressure in the memory and clear the cache if necessary. You can either answer didReceiveMemoryWarning or add yourself as an observer to the UIApplicationDidReceiveMemoryWarningNotification notification UIApplicationDidReceiveMemoryWarningNotification .

  • Make sure that your cached images have a thumbnail size, otherwise you will always stutter a little in your user interface (if you need a resizing algorithm, let us know) and it will be unnecessary to use memory;

  • When you send an update of your image back to the main queue, you should do it asynchronously (why does this background queue freeze and bind resources, since it expects the block to be sent back to the main queue to finish ... this is especially a problem when you have there are several images created with fast scrolling); and

  • When you send back to the main queue, you should check to make sure that the cell you get from cellForRowAtIndexPath is not nil (because if the logic for loading cells is too redundant (for example, on slower devices) you could theoretically have the corresponding cell scrolling from the screen and your algorithm might work).

I use an algorithm very similar to yours with almost the same GCD structure (with the above warnings), and this is a pretty smooth scroll even on older devices. If you want me to post the code, I'm happy.

If you are still having problems, the CPU Profiler is great for identifying bottlenecks and letting you know where you should focus your attention. There are some great WWDC sessions on the Internet that focus on using tools to identify performance bottlenecks, and I found them very useful for gaining experience with the tools.

Here is my code. In viewDidLoad I initialize my image cache:

 - (void)initializeCache { self.imageCache = [[NSCache alloc] init]; self.imageCache.name = @"Custom Image Cache"; self.imageCache.countLimit = 50; } 

And then I use this in my tableView:cellForRowAtIndexPath :

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"ilvcCell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; // set the various cell properties // now update the cell image NSString *imagename = [self imageFilename:indexPath]; // the name of the image being retrieved UIImage *image = [self.imageCache objectForKey:imagename]; if (image) { // if we have an cachedImage sitting in memory already, then use it cell.imageView.image = image; } else { cell.imageView.image = [UIView imageNamed:@"blank_image.png"]; // the get the image in the background dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // get the UIImage UIImage *image = [self getImage:imagename]; // if we found it, then update UI if (image) { dispatch_async(dispatch_get_main_queue(), ^{ // if the cell is visible, then set the image UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath]; if (cell) cell.imageView.image = image; [self.imageCache setObject:image forKey:imagename]; }); } }); } return cell; } 

and

 - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; [self.imageCache removeAllObjects]; } 

As an aside, another optimization that you might consider would be preloading your cached images in a separate queue, rather than uploading images to a separate stream on time. I do not think this is necessary, because for me it seems more than fast enough, but this is another option for accelerating the user interface.

+12
source

The missing step is to update the model using the extracted image. Be that as it may, each time you perform a new load for each cell. A model is a suitable place to cache the result of a relatively expensive load. Can you add a Beer.image property?

Then your configuration code will look like this:

 Beer *beer = (Beer *) [self.fetchedResultsController objectAtIndexPath:indexPath]; cell.displayBeerName.text = beer.name; // did we already cache a copy of the image? if (beer.image != nil) { // good. use it. this will run quick and this will run most of the time cell.beerImage.image = beer.image; } else { // it must be the first time we've scrolled by this beer. do the expensive // image init off the main thread cell.beerImage.image = nil; // set a default value here. nil is good enough for now [self loadImageForBeer:beer atIndexPath:indexPath]; } 

Moved the loader logic here for clarity ...

 - (void)loadImageForBeer:(Beer *)beer atIndexPath:(NSIndexPath *)indexPath { dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); dispatch_async(queue, ^{ UIImage *image = [UIImage imageWithContentsOfFile:beer.imagePath]; beer.image = image; dispatch_sync(dispatch_get_main_queue(), ^{ UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath]; cell.beerImage.image = image; }); }); } 
+1
source

There is not much you can do here for bootstrapping, you are as fast as you get. If it's still too slow, try uploading smaller images if you can.

Few things:

First, be careful with -imageWithContentsOfFile, it will not cache anything. You get a full hit every time you upload an image, unlike -imageNamed, which will save the image in cache. Of course, you can cache this in your domain object, but personally I strongly advise against this. Your memory will go through the roof, forcing you to implement your own cache expiration mechanism, while Apple has a very good image cache via -imageNamed. I would be surprised if you could work better than an apple on all three device families :)

Then you break the UITableView fly pattern here:

 dispatch_sync(dispatch_get_main_queue(), ^{ cell.beerImage.image = image; beer.image = image; [cell setNeedsLayout]; }); 

Ask the table view to provide the cell with a specific index, rather than grab the cell in the block: by the time the image is loaded, this cell instance could be reused for another pointer path, and you would display the image in the wrong cell.

And no need here for -setNeedsLayout, just change the image.

Edit: screaming! I missed the obvious thing with table images. What are the sizes of your images, what is the size of the image, and what is the content mode of the image? If your images have a completely different size than the image type, and you ask to resize the image, this will happen in the main thread, and you will get massive performance. Resize the image to an image without a stream, after downloading (a quick Google search will give you the main graphic code for this).

+1
source

You can see this question that previously answered stack overflow.

UIImage in scrollable table uitableViewcell slows down

or try this code

 - (void)configureCell:(BeerCell *)cell atIndexPath:(NSIndexPath *)indexPath { Beer *beer = (Beer *) [self.fetchedResultsController objectAtIndexPath:indexPath]; cell.displayBeerName.text = beer.name; UIImage *image = [UIImage imageWithContentsOfFile:beer.imagePath]; cell.beerImage.image = image; [cell setNeedsLayout]; } 
-1
source

Source: https://habr.com/ru/post/920497/


All Articles