In UITableView, the best way to undo GCD operations for cells that have left the screen?

I have a UITableView that loads images from a URL into cells asynchronously using GCD. The problem is that the user scrolls over 150 lines, 150 queue and run operations. I want to withdraw / cancel those that made their way and left the screen.

How to do it?

My code at the moment (pretty standard):

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath // after getting the cell... dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ if (runQ) { NSString *galleryTinyImageUrl = [[self.smapi getImageUrls:imageId imageKey:imageKey] objectForKey:@"TinyURL"]; NSData *imageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:galleryTinyImageUrl]]; dispatch_async(dispatch_get_main_queue(), ^{ if (imageData != nil) { UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; cell.imageView.image = [UIImage imageWithData:imageData]; } }); } }); 

runQ is BOOL ivar I set to NO on viewWillDisappear , which (I think) causes the queue to be quickly reset when this UITableView pops the navigation controller.

So, back to my original question: how do I cancel image retrieval operations for cells that have left the screen? Thanks.

+6
source share
3 answers

First, do not perform queue operations while scrolling. Instead, load images only for visible lines in viewDidLoad and when the user stops scrolling:

 -(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { for (NSIndexPath *indexPath in [self.tableView indexPathsForVisibleRows]) { [self loadImageForCellAtPath:indexPath]; } } 

If you still want to cancel the download for invisible cells, you can use NSBlockOperation instead of GCD:

 self.operationQueue = [[[NSOperationQueue alloc] init] autorelease]; [self.operationQueue setMaxConcurrentOperationCount:NSOperationQueueDefaultMaxConcurrentOperationCount]; // ... -(void)loadImageForCellAtPath:(NSIndexPath *)indexPath { __block NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ if (![operation isCancelled]) { NSString *galleryTinyImageUrl = [[self.smapi getImageUrls:imageId imageKey:imageKey] objectForKey:@"TinyURL"]; NSData *imageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:galleryTinyImageUrl]]; dispatch_async(dispatch_get_main_queue(), ^{ if (imageData != nil) { UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; cell.imageView.image = [UIImage imageWithData:imageData]; } }); } }]; NSValue *nonRetainedOperation = [NSValue valueWithNonretainedObjectValue:operation]; [self.operations addObject:nonRetainedOperation forKey:indexPath]; [self.operationQueue addOperation:operation]; } 

Here operations are NSMutableDictionary . When you want to cancel the operation, you will receive it with the indexPath cell, cancel it and remove it from the dictionary:

 NSValue *operationHolder = [self.operations objectForKey:indexPath]; NSOperation *operation = [operationHolder nonretainedObjectValue]; [operation cancel]; 
+7
source

You cannot cancel GCD operations after they are submitted. However, you can check the sending blocks to see if the code should continue or not. In -tableView: cellForRowAtIndexPath: you can do something like this

 UIImage *image = [imageCache objectForKey:imageName]; if(image) { cell.imageView.image = image; else { dispatch_async(globalQueue, ^{ //get your image here... UIImage *image = ... dispatch_async(dispatch_get_main_queue(), ^{ if([cell.indexPath isEqual:indexPath){ cell.indexPath = nil; cell.imageView.image = image; [cell setNeedsLayout]; } }); [imageCache setObject:image forKey:imageName]; }); } 

Mostly use the image cache (NSMutableDictionary) and try to capture the image. If you don’t have one, send a block and receive it. Then send back to the main thread, check the pointer path, and then set the image and finally cache the image. This example uses a custom tableview cell, which has an index path stored as a property in it.

So, this is a little extra work to do this job well, but it's worth it.

+2
source

If you are using dedicated cells, the best way is to undo the GCD in

- [UITableViewCell prepareForReuse]

0
source

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


All Articles