异步加载图像,奇怪的问题

时间:2014-02-13 11:38:25

标签: ios objective-c uitableview nsurl

有我的问题:

我有自定义类,它解析XML并获取我需要用作字符串URL的字符串,现在我修改了我的代码如下:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];

UILabel *labelText = (UILabel *)[cell viewWithTag:1000];
    labelText.text = [[self.listOfPlaceDetails objectAtIndex:indexPath.row] objectForKey:@"name"];




    dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(concurrentQueue, ^{
        NSURL *imageURL = [NSURL URLWithString:[[self.listOfPlaceDetails objectAtIndex:indexPath.row]objectForKey:@"imageCell"]];

        NSData *image = [[NSData alloc] initWithContentsOfURL:imageURL];

        dispatch_async(dispatch_get_main_queue(), ^{



            cell.imageView.image = [UIImage imageWithData:image];
        });
    });

 return cell;
}

这非常简单,但是,我得到了不可预知的错误!在滚动表期间,图像开始混乱地变化,有时它显示3个或更多图像并且最终图像是正确的,有时最终(正确)图像根本不出现。此外,当第一次显示表格时,它实际上是空白的,所以我需要将其向下滚动,然后再向上滚动以查看我的图像!

为了解决这个问题,我添加了以下代码,以确定我的图像链接对于该indexPath是否正确:

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{

    NSURL *imageURL = [NSURL URLWithString:[[self.listOfPlaceDetails objectAtIndex:indexPath.row]objectForKey:@"imageCell"]];
    NSLog(@"%@", imageURL);
}

当我点击任何单元格时,它确实在控制台日志中显示正确的链接,但单元格上的图像是之前显示的图像之一(无效),并且它不是该链接的图像。如何解决这些奇怪的错误?

任何建议都将不胜感激,谢谢。

2 个答案:

答案 0 :(得分:1)

当你出列一个单元格对象时,大部分时间你都会得到一个重用的单元格,即之前一次或多次由tableView:cellForRowAtIndexPath:配置的单元格。

要想象您的案例中发生了什么,请在执行长而快速的滚动时考虑单个单元格的一个可能的事件序列:

  1. 创建单元格并在后台旋转图像加载
  2. 单元格在屏幕上滚动,因此添加到表格视图的缓存中,准备出列。此时不取消图像加载
  3. 单元格出列,图像载荷在后台旋转
  4. 步骤2和3重复几次
  5. 单元格可见,但是几个图像加载任务现在正在完成,每个任务都在使用加载的图像更新单元格的imageView。这确实看起来像每个加载操作完成时图像都在混乱地变化。
  6. (更重要的是,对于并发队列,无法保证图像加载将按照它们启动的顺序完成 - 您可能无法获得正确的最终图像!)

    那我们该怎么做呢?现在我们了解了这个问题,有很多不同的解决方案。一个非常简单的解决方案(我并不是真的建议)是在设置图像时检查单元格的标签文本是否与该indexPath的值匹配:

    if ([labelText.text isEqualToString:[[self.listOfPlaceDetails objectAtIndex:indexPath.row] objectForKey:@"name"]]) {
       cell.imageView.image = [UIImage imageWithData:image];
    }
    

    显然,这假设所有地方细节都有唯一的名称。

    更好的解决方案可能是创建一个处理图像下载的对象,并且可以注册/取消注册单元格以处理下载完成。此对象可以强制执行单元格无法等待多个图像加载的条件。正如@Leena指出的那样,缓存是一个好主意,这个对象也可能对此负责。

    对于空白图像,在设置图像后调用[cell setNeedsLayout]应该将其排序。

答案 1 :(得分:1)

默认属性" imageView"不会被添加到单元格,直到它的(imageView' s)属性"图像"是零(你可以检查cell.imageView.superview也将是零)。 这就是为什么你的tableView在加载时是空白的,并且还加载了单元格的所有图像。 所以当你向下滚动(或向上)时,将重新加载单元格,它们的" imageView"将有图像数据。这就是他们在单元格上的原因,你可以看到它们。

另一个问题是您的图像一直在闪烁。这是因为你的细胞出列了。 因此,当下载并设置第一个单元格的图像时,它将不会显示,直到您重新出现单元格的子视图(例如通过调用[cell setNeedsLayout] ;-)。 当您向下滚动表格时,您的第一个单元格(现在带有图像)将从" tableView"并将成为最后一个单元格,然后您的第一个单元格将显示实际属于第一行的图像。 同时你开始下载这个(最后一行)的图像,下载后你将设置它。闪光发生时,这就是一个因素。