6

我有一个UITableView使用 GCD 将 URL 中的图像异步加载到单元格中的方法。问题是如果用户轻弹过去 150 行,则 150 个操作排队并执行。我想要的是出列/取消那些过去并离开屏幕的。

我该怎么做呢?

我此时的代码(非常标准):

- (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 是BOOL我设置为NOon的 ivar viewWillDisappear,(我认为)当它UITableView从导航控制器中弹出时,它具有快速清除队列的效果。

那么,回到我最初的问题:如何取消已离开屏幕的单元格的图像获取操作?谢谢。

4

3 回答 3

7

首先,不要在滚动时排队操作。viewDidLoad相反,仅在用户停止滚动时加载可见行的图像:

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

如果您仍然希望能够取消对不可见单元格的加载,您可以使用NSBlockOperation而不是 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];
}

operations是一个NSMutableDictionary. 当您想取消操作时,您可以通过单元格的 检索它indexPath,取消它,然后将其从字典中删除:

NSValue *operationHolder = [self.operations objectForKey:indexPath];
NSOperation *operation = [operationHolder nonretainedObjectValue];
[operation cancel];
于 2012-05-02T17:17:25.350 回答
2

GCD 操作在发送后无法取消。但是,您可以检查调度块以查看代码是否需要继续。在 -tableView:cellForRowAtIndexPath: 你可以做这样的事情

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];
    });
}

基本上使用图像缓存(NSMutableDictionary)并尝试抓取图像。如果你没有它,那么调度一个块并获取它。然后分派回主线程,检查索引路径,然后设置图像,最后缓存图像。此示例使用自定义 tableview 单元格,该单元格将索引路径作为属性存储在其中。

因此,为了使这项工作顺利进行,需要做一些额外的工作,但这是值得的。

于 2012-05-02T15:47:00.350 回答
0

如果您使用出队的单元格,最好的方法是取消 GCD

-[UITableViewCell prepareForReuse]

于 2012-05-02T06:19:49.550 回答