在后台从 URL 加载 tableview 图像会导致重复

Posted

技术标签:

【中文标题】在后台从 URL 加载 tableview 图像会导致重复【英文标题】:Loading tableview images from URL in background causes duplicates 【发布时间】:2012-03-28 15:56:25 【问题描述】:

我有一个表格视图,其中填充了搜索词的结果。许多结果包含需要从 URL 加载的图像,但不是全部。我最初让它在主线程的cellForRowAtIndexPath 方法中从 URL 中抓取图像,效果很好,但是它使 tableview 的滚动变得不稳定,因为它暂时“卡”在每个图像上。

所以,我决定尝试在后台线程中加载图像。这是我的cellForRowAtIndexPath 方法。 URL 存储在 resultsArray 中,索引对应于单元格的行。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

    [sBar resignFirstResponder];
    if (indexPath.row != ([resultsArray count])) 
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ResultCell"];
    UIImageView * bookImage = (UIImageView *)[cell viewWithTag:102];
        //set blank immediately so repeats are not shown
        bookImage.image = NULL;

        //get a dispatch queue
        dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        //this will start the image loading in bg
        dispatch_async(concurrentQueue, ^        
            NSData *image = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:[[resultsArray objectAtIndex:indexPath.row] thumbnailURL]]];

            //this will set the image when loading is finished
            dispatch_async(dispatch_get_main_queue(), ^
                bookImage.image = [UIImage imageWithData:image];

                if(bookImage.image == nil)
                            
                                bookImage.image = [UIImage imageNamed:@"no_image.jpg"];
                            
            );
        ); 

        return cell;
    
    // This is for the last cell, which loads 10 additional items when touched
    // Not relevant for this question, I think, but I'll leave it here anyway
    else
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"More"];
        UILabel *detailLabel = (UILabel *)[cell viewWithTag:101];
        detailLabel.text = [NSString stringWithFormat:@"Showing %d of %d results", [resultsArray count], [total intValue]];
        if ([UIApplication sharedApplication].networkActivityIndicatorVisible == NO) 
            cell.userInteractionEnabled = YES;
        
        return cell;

    

当 tableview 缓慢滚动时,图像都加载到相应的单元格中,我可以确认这一点,因为图像与单元格的标题匹配,但我省略了除图像之外的所有设置以缩短此问题。但是,当我滚动得更快时,尤其是当我模拟慢速互联网连接时,图像开始加载到错误的单元格中。我认为这与重用单元格有关,因为如果我快速向顶部滚动,刚刚离开视图的单元格的图像通常会在刚刚进入的单元格中结束。我以为 bookImage.image = NULL; line 将确保不会发生这种情况,但我想不会。我想我不太了解后台线程,当最终从 URL 加载图像时,它是否忘记了它打算用于哪个单元格?你知道会发生什么吗?感谢您的任何反馈!

【问题讨论】:

【参考方案1】:

猜测:

    单元格 A 已加载并开始异步请求 A。 表格已滚动,单元格 A 在请求完成之前滚动到屏幕外。 单元格 A 被回收并作为单元格 B 出队。 单元格 B 开始异步请求 B。 异步请求A完成,更新cell A(现在是cell B)的图像视图。

解决此问题需要保留异步请求的句柄并在单元出队时取消它。

【讨论】:

好的,有道理,感谢您的反馈!但是我如何跟踪哪个单元格正在出队以及哪个请求对应于它?【参考方案2】:

我的方法(依赖于 AFNetworking 库)。将以下 zip 文件中的类包含到您的项目中:

http://dl.dropbox.com/u/6487838/imageloading.zip

创建您自己的自定义单元格。代码可能如下所示(检查-setComment:-willMoveToSuperview: 方法):

#import "CommentCell.h"
#import "CommentCellView.h"
#import "MBImageLoader.h"


@interface CommentCell ()
@property (nonatomic, strong) UIImageView     *imageView;
@property (nonatomic, strong) CommentCellView *cellView;
@property (nonatomic, assign) CellStyle       style;
@end


@implementation CommentCell

@synthesize cellView, style = _style, imageView, comment = _comment, showCounter;

- (id)initWithStyle:(CellStyle)style reuseIdentifier:(NSString *)reuseIdentifier;

    self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier];
    if (self)
    
        self.selectionStyle = UITableViewCellSelectionStyleNone;
        self.opaque = YES;
        self.style = style;
        self.showCounter = YES;


        CGFloat width = 53.0f;
        CGFloat x = (style == CellStyleRight) ? 320.0f - 10.0f - width : 10.0f;
        CGRect frame = CGRectMake(x, 10.0f, width, 53.0f);
        self.imageView = [[UIImageView alloc] initWithFrame:frame];
        imageView.image = [UIImage imageNamed:@"User.png"];
        [self.contentView addSubview:imageView];


        self.cellView = [[CommentCellView alloc] initWithFrame:self.frame cell:self];
        cellView.backgroundColor = [UIColor clearColor];
        [self.contentView addSubview:cellView];
    
    return self;


- (void)dealloc

    self.comment = nil;
    self.cellView = nil;
    self.imageView = nil;


- (void)willMoveToSuperview:(UIView *)newSuperview

    [super willMoveToSuperview:newSuperview];

    if (!newSuperview)
    
        [[MBImageLoader sharedLoader] cancelLoadImageAtURL:_comment.iconURL forTarget:self];
    


- (void)setSelected:(BOOL)selected animated:(BOOL)animated

    [super setSelected:selected animated:animated];

    // Configure the view for the selected state


- (void)setFrame:(CGRect)newFrame 

    [super setFrame:newFrame];

    CGRect bounds = self.bounds;
    bounds.size.height -= 1; 
    cellView.frame = bounds;


- (void)setNeedsDisplay 

    [super setNeedsDisplay];

    [cellView setNeedsDisplay];


- (void)setComment:(MBComment *)comment

    if (_comment == comment) 
        return;

    _comment = comment;

    if (comment.icon == nil)
    
        imageView.image = nil;

        [[MBImageLoader sharedLoader] loadImageForTarget:self withURL:comment.iconURL success:^ (UIImage *image) 
         
             comment.icon = image;

             imageView.alpha = 0.0f;
             imageView.image = image;

             [UIView animateWithDuration:0.5f delay:0.0f options:UIViewAnimationOptionAllowUserInteraction animations:^ 
                 imageView.alpha = 1.0f;                 
              completion:^ (BOOL finished) ];             

          failure:^ (NSError *error) 
         
             DLog(@"%@", error);
         ];
    
    else 
    
        imageView.image = comment.icon;
    

    [self setNeedsDisplay];


@end

【讨论】:

【参考方案3】:

我是怎么做到的。

cellForRowAtIndexPath:方法:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath     *)indexPath 

static NSString *cellIdentifier = @"CellIdentificator";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) 
    cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];

[cell.imageView setImage:nil];

NSData *const cachedImageData = [self.cache objectAtIndex:indexPath.row];
if ([cachedImageData isKindOfClass:[NSData class]]) 
    [cell.imageView setImage:[UIImage imageWithData:cachedImageData]];
 else 
    [self downloadAndCacheImageForIndexPath:indexPath];


return cell;

downloadAndCacheImageForIndexPath:方法:

- (void)downloadAndCacheImageForIndexPath:(NSIndexPath *)indexPath 

dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^
    NSString *const imageStringURL = [NSString stringWithFormat:@"%@%.2d.png", IMG_URL, indexPath.row];

    NSData *image = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:imageStringURL]];
    [self.cache replaceObjectAtIndex:indexPath.row withObject:image];

    dispatch_async(dispatch_get_main_queue(), ^
        [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:indexPath.row inSection:0]]withRowAnimation:UITableViewRowAnimationNone];
    );
    [image release];
);

self.cacheNSMutableArray,我用它来存储 NSData 用于加载的图像。 self.cache 预装了[NSNull null]s 的图片数量

BR. 尤金。

【讨论】:

以上是关于在后台从 URL 加载 tableview 图像会导致重复的主要内容,如果未能解决你的问题,请参考以下文章

使用后台队列从 url 加载图像仍然不够快

tableview中的图像会加载另一个图像几秒钟

将图像从Firebase加载到我的表视图时出错

通过 JSON 将图像数组加载到 tableview 图像视图中

如何从 URL 在 TableView 中添加图像

数据不会从 JSON 解析加载到某些 Tableview 单元格上