5

我知道这已经被问过很多次了,但请耐心等待,因为我已经尝试了几天来寻找解决方案。

我有一个 rss 解析器,一个表视图,其中:文本、详细信息和图像设置为 AccessoryDisclosureIndicator.view。

图像通过简单的 GCD 异步加载很好:快速滚动数百个结果。如果我有良好的连接,没有延迟,没有错误。

问题是,一瞬间 - 它们在加载时闪烁,因为单元正在被重复使用。此外,如果连接不佳,有时会留下散乱的图像,但文本/细节是正确的,只有图像是旧的......所以让我重复一遍,文本/细节更新很好,永远不会重新排队错误,只是图像有时很少会在连接不良/快速来回滚动时出现错误排队。

我的问题,有人可以帮我缓存/标记 cell.accs.views 吗?我尝试设置 cellID,但在实现时遇到了问题。如果连接永远不会变慢,我的下面的代码效果很好,只是在重新排队一个我不介意的单元格时有轻微的闪烁。

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

if (cell == nil) {
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];

    [cell.textLabel setNumberOfLines:3];
    cell.textLabel.font = [UIFont boldSystemFontOfSize:14.0];

    [cell.detailTextLabel setNumberOfLines:3];
    cell.detailTextLabel.font = [UIFont systemFontOfSize:12.0];
    cell.detailTextLabel.textColor = [UIColor blackColor];
}

RSSItem * rssItem = (RSSItem *)(_rssParser.rssItems)[indexPath.row];

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,  0ul);
dispatch_async(queue, ^{
    //This is what you will load lazily
    NSData   *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:rssItem.imageURL]];
    dispatch_sync(dispatch_get_main_queue(), ^{

        UIImageView *accImageView = [[UIImageView alloc] initWithImage:[UIImage imageWithData:data ]];

        [accImageView setFrame:CGRectMake(0, 0, accImageView.image.size.width, 92)];
        cell.accessoryView = accImageView;

        //[cell setNeedsLayout];//not needed
    });
});

[cell.textLabel setText:rssItem.title];
[cell.detailTextLabel setText:rssItem.summary];

return cell;}
4

4 回答 4

10

关键问题是,在您dispatch_async获取新图像之前(您知道这可能需要一些时间,甚至在连接速度较慢的情况下可能需要几秒钟),重置旧图像或使用占位符图像加载它。

两个次要问题:

  1. 请注意,如果使用非常慢的网络,当图像返回时,您可能已经将单元格从屏幕上滚动(例如,假设您向上轻弹滚动视图,然后,当图像仍在显示时)加载后,您将其弹回)。在尝试设置图像之前,您确实应该确保单元格仍然可见。

    您可以通过检查(在主队列上)单元格是否可见来做到这一点:

    dispatch_async(dispatch_get_main_queue(), ^{
        UITableViewCell *updateCell = [tableView cellForRowAtIndexPath:indexPath];
        if (updateCell != nil) {
            // update image in `updateCell`
        }
    });
    

    请注意,此UITableView方法 ,cellForRowAtIndexPath不应与UITableViewDataSource方法 ,相混淆tableView:cellForRowAtIndexPath:。前者只是检查单元格是否可见,如果是,则返回指向它的指针。显然,后者是您在问题中引用的方法,用于创建和配置表格视图单元格。

    另请注意,您使用NSIndexPath来检查单元格是否仍然可见的这个想法假设在此期间不可能在表视图中插入任何其他行。这通常不是一个有效的假设,因此您甚至可能必须返回基础模型并重新计算indexPath此单元格的 ,然后再继续执行上述逻辑以查看该单元格是否仍然可见。

  2. 您可能不想使用全局队列,因为在非常慢的网络上,您的请求可能会开始积压,最终您可能会收到大量并发网络请求。这是一个问题,因为 (a) 无论如何,系统一次不能执行超过 4 或 5 个并发请求;(b) 您将用完 iOS 提供的有限数量的工作线程。你真的不应该有超过 4 或 5 个并发请求(如果你有更多,你不仅不会看到性能提升,而且它们实际上会阻塞并最终超时)。我建议您考虑使用NSOperationQueue设置maxConcurrentOperationCount为四或五的 a。

    此外,这提供了最后的优化,特别是使这些NSOperation子类请求可取消(并且,显然,使取消逻辑实际上取消了其中包含的网络请求)。因此,如果您快速滚动到表中的第 100 行,您真的不希望可见单元格等待前 99 行的图像下载首先完成。如果您取消对滚动到屏幕外的单元格的图像请求,则可以解决此问题。

如果你想诊断你的应用程序在一个非常慢的网络连接上的行为,我建议你获得网络调节器,它可以让你的 Mac 在模拟器上模拟从全速到质量非常差的边缘网络的任何东西。在 Xcode 的 Xcode 菜单中,选择“Open Developer Tool...”,然后选择“More Developer Tools”。登录后,您会看到“Hardware IO Tools for Xcode”选项。网络调节器工具就在那里。您可以在最坏的情况下测试您的应用程序,这不仅可以让您考虑我上面概述的两个问题,还可以考虑更高级的功能,例如图像缓存等。

您真的想缓存图像,至少缓存到持久存储,理想情况下缓存到 RAM 和持久存储。有一些UIImageView类别可以解决上述异步问题,并提供一些缓存:您可能会考虑的两个是SDWebImageAFNetworking

于 2013-05-02T03:16:23.867 回答
1

您只需在重用单元格时清除旧图像。

最后,您可以添加:

cell.accessoryView = nil;
于 2013-05-02T03:14:08.010 回答
1

问题是,一瞬间 - 它们在加载时闪烁,因为单元正在被重复使用

但这就是你的答案。该单元格正在被重用,我在您的代码中看不到任何删除图像的内容。如果该图像不是该行的正确图像(因为它在不同的行中重复使用),您需要现在cell.accessoryView设置为 nil(或带有占位符图像的图像视图)。正确的图像直到稍后才会出现()。dispatch_async

但是,您使用的代码存在很大缺陷,因为它每次都从 Internet 获取图像,即使我们已经看到了这一行并因此已经下载了图像。我的书有更好的代码来加载表格视图中的图像,必要时下载它们并在之后存储它们。它还向您展示了如何在图像到达之前将行从表格中滚动出来时停止下载,从而节省带宽:

http://www.aeth.com/iOSBook/ch37.html#_http_requests

向下滚动到“假设,例如,您需要下载图像以用作 UITableView 单元格中的缩略图。让我们考虑如何根据需要延迟提供这些图像。”

于 2013-05-02T03:14:16.133 回答
0

这是我使用 SDWebImage 所做的,我被迫为所有单元格使用占位符,我只是将没有图像的单元格设置为一个小的 V 形图标。按预期工作。谢谢大家。

RSSItem * rssItem = (RSSItem *)(_rssParser.rssItems)[indexPath.row];

if (rssItem.imageURL == nil ){
    UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 20, 20)];
    [imageView setImageWithURL:[NSURL URLWithString:rssItem.imageURL] placeholderImage:[UIImage imageNamed:@"chevron.png"]];
    cell.accessoryView = imageView;
}else{
    UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 110, 96)];
    imageView.contentMode  = UIViewContentModeScaleAspectFit;
    [imageView setImageWithURL:[NSURL URLWithString:rssItem.imageURL] placeholderImage:[UIImage imageNamed:@"loading.png"]];
    cell.accessoryView = imageView;
}

[cell.textLabel setText:rssItem.title];
[cell.detailTextLabel setText:rssItem.summary];

return cell;
于 2013-05-10T17:42:33.530 回答