1

我有一个表格视图,其中包含正在使用大中央调度延迟加载的图像。我使用了一个异步队列,里面有两个串行队列,第一个用于下载图像,第二个队列用于将图像设置到单元格。这种方法在滚动时似乎有滞后行为。

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        __block UIImage *image = nil;
        dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:artist.imImage]]];
        });
        dispatch_sync(dispatch_get_main_queue(), ^{
            cell.artistImage.image = image;

        });

    });

然后我尝试使用单个异步队列来下载图像,然后将主队列放入内部并设置图像。即使我觉得这种方法不太合适。我想我在这里遗漏了一些东西。

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:artist.imImage]]];
        dispatch_async(dispatch_get_main_queue(), ^{
            cell.artistImage.image = image;
        });

    });

是我在这里遗漏了什么还是其他问题?

4

7 回答 7

2

UIKit 不是线程安全的,因此您应该将所有 UIKit 操作排队到主队列。目前尚不清楚使用第二种解决方案会遇到什么样的问题:

    UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:artist.imImage]]];
    dispatch_async(dispatch_get_main_queue(), ^{
        cell.artistImage.image = image;
    });

但我建议在主线程中实例化 UIImage:

    NSData* data = [NSData dataWithContentsOfURL:[NSURL URLWithString:artist.imImage]];
    dispatch_async(dispatch_get_main_queue(), ^{
        UIImage *image = [UIImage imageWithData:data];
        cell.artistImage.image = image;
    });
于 2012-07-30T08:46:34.487 回答
1

您拥有的第二个解决方案是要走的路——创建比必要更多的任务是没有意义的(“必要”的价值取决于您当然想要做的事情)。

但是,您可能会看到一个问题:

  1. 单元格出现
  2. 您在 GCD 任务中请求图像
  3. 用户向上滚动
  4. 细胞被重复使用
  5. 您在 GCD 任务中请求新图像
  6. 原始请求(来自第 2 步)完成并使用“旧”图像更新单元格

在使用图像更新之前,您需要检查单元格是否仍然是“最新的”。

于 2012-07-30T10:30:01.490 回答
1

我这样做了,但我在我的实现中使用了NSOperation和。NSOperationQueue当单元格离开屏幕时,它不再需要图像,因此它取消了异步下载+展开NSOperation。队列的最大值非常低(也限制了内存需求)。调整后非常流畅(并且在服务器上的图像尺寸正确之后)。

如果不消耗大量空间,还可以考虑缓存您可以缓存的内容。

于 2012-07-30T11:42:45.537 回答
1

您的第二个代码段是要走的路。我相信斯蒂芬达林顿给了你问题的原因,我想提出一种不同的方式来构建你的代码(因为它是我这样做的方式并且它现在工作得很好)。

因此,当一个单元需要图像时,它会向某个数据管理器请求它。如果图像在那里,它会被返回。如果它不存在,则启动获取操作。当图像到达并被创建时,您不应该尝试设置单元格,而是在图像保存在缓存中时在主线程上发布通知(NSDictionary 是保存它们的一种方法,以 URL 或名称作为键) .

控制您的 tableView 的对象会侦听通知。当一个到达时,它会查看所有可见的单元格,如果一个图像丢失并且它刚刚到达,则图像会得到更新。否则它什么也不做。

当表格滚动时,tableView 要求一个新的单元格,您总是会看到图像是否在缓存中,如果是,则使用它。否则,您将请求图像。

为避免对图像的多次请求(网络获取),当您创建一个图像时,您需要注意这一事实。一种方法是在开始获取时将 imageName 或 URL 放入 NSMutableSet,并在获取图像时将其删除。

这比您正在做的工作要多一些,但结果是一个非常流畅且响应迅速的 GUI。

于 2012-07-30T12:14:17.080 回答
1

不幸的是,您不应该使用 Grand Central 以这种方式更新 UI。大中央调度阻塞了主线程的消息队列,即使它不应该这样做。

试试这个来证明我的观点,拿你的异步代码,而不是使用 dispatch_async,做一个简单的消息调度,比如

[self performSelector:@selector(blabla:) withObject:nil afterDelay:0]

您会发现您的代码运行良好,但 GCD 实际上是在停止向 GUI 发送消息循环。

如果在后台进程上使用 GCD 会发生这种情况,有时它有时会起作用。

大多数情况下,当您已经在 GUI 线程中时(如用户按下按钮操作),GCD 将严重延迟 GUI 更新直到完成。

学习运行循环在 iOS 中是如何工作的,比把所有东西都扔给 GCD 更有效率。

投它。

于 2012-07-30T14:56:51.747 回答
1

我怀疑您看到的性能问题是因为您产生了不必要的网络活动。

我会使用字典来缓存图像。而不是在单元格出队时加载图像,我会做 on viewDidLoad(或每当你的数据模型初始化时),并且 on scrollViewDidEndDecelerating:。否则,您会为用户刚刚滚动过去的所有单元格排队图像请求。

-(void)viewDidLoad {
    [super viewDidLoad];
    [self initializeData];
    self.images = [NSMutableDictionary dictionary];
    [self loadVisibleImages];
}

-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    if (scrollView == self.tableView) {
        [self loadVisibleImages];
    }
}

-(void)loadVisibleImages {
    for (ArtistCell *cell in [self.tableview visibleCells]) {
        NSURL *artistURL = [NSURL URLWithString:cell.artist.imImage];
        UIImage *artistImage = [self.images objectForKey:artistURL];
        if (artistImage) {
            cell.artistImage.image = artistImage;
        } else {
            cell.artistImage.image = self.loadingImage;
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:artist.imImage]]];
                [self.images setObject:image forKey:artistURL];
                dispatch_sync(dispatch_get_main_queue(), ^{
                    [cell setNeedsLayout];
                });
            });
        }
    }
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    ... dequeue the cell, do all your other setup
    NSURL *artistURL = [NSURL URLWithString:cell.artist.imImage];
    UIImage *artistImage = [self.images objectForKey:artistURL];
    if (artistImage) {
        cell.artistImage.image = artistImage;
    } else {
        cell.artistImage.image = self.loadingImage;
    }
}
于 2012-07-30T22:39:51.867 回答
0

我认为你应该做的是使用 SDWebImage 库:https ://github.com/rs/SDWebImage

它将为您完成所有这些并在本地缓存生成的图像,并负责从缓存中拒绝 LRU 图像......

这真是太棒了。

于 2012-10-23T05:48:25.007 回答