In a UITableView, best method to cancel GCD operations for cells that have gone off screen?

天大地大妈咪最大 提交于 2019-12-03 07:53:18

First, don't queue operations while scrolling. Instead, load images for just the visible rows 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 be able to cancel loading for invisible cells, you could 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 is an NSMutableDictionary. When you want to cancel an operation, you retrieve it by the cell's indexPath, cancel it, and remove it from the dictionary:

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

You cannot cancel GCD operations after they've been dispatched. You can however check in the dispatch blocks to see if the code needs to continue on 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];
    });
}

Basically use a image cache (NSMutableDictionary) and try to grab the image. If you don't have it then dispatch a block and get it. Then dispatch back to the main thread, check the index path and then set the image and finally cache the image. This example uses a custom tableview cell that has the index path stored with it as a property.

So its a little extra work, to make this work well, but its worth it.

If you are using dequeued cells the best approach is to cancel GCD in

-[UITableViewCell prepareForReuse]

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!