6

我正在将图像加载到表格视图单元格中,每个单元格都有一个图像。我已经为下面的代码改编了几个教程,但我的速度仍然很慢。

我正在从文档目录加载这些图像。有关如何加快此过程的任何提示或想法?

编辑修改后的代码:

Beer *beer = (Beer *) [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.displayBeerName.text = beer.name;

// did we already cache a copy of the image?
if (beer.image != nil) {
    // good.  use it.  this will run quick and this will run most of the time
    cell.beerImage.image = beer.image;
} else {
    // it must be the first time we've scrolled by this beer.  do the expensive
    // image init off the main thread

    cell.beerImage.image  = nil;   // set a default value here.  nil is good enough for now

    [self loadImageForBeer:beer atIndexPath:indexPath];
}
- (void)loadImageForBeer:(Beer *)beer atIndexPath:(NSIndexPath *)indexPath {

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
    dispatch_async(queue, ^{

        UIImage *image = [UIImage imageWithContentsOfFile:beer.imagePath];
        beer.image = image;

        dispatch_sync(dispatch_get_main_queue(), ^{
            UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
            cell.beerImage.image = image;
        });
    });
}
4

4 回答 4

12

你的算法看起来不错。您已经避免了许多典型的陷阱。如果您仍然遇到 UI 性能问题,我建议您做一些事情:

  1. 您应该尝试将图像缓存在内存中。您可以使用NSMutableArrayor NSMutableDictionary,但在ios 应用程序上缓存图像的最佳方式是?Caleb 讨论了该NSCache课程的优点,从而简化了流程。如果您缓存图像,请确保响应内存压力并在必要时清除缓存。您可以回复didReceiveMemoryWarning或将自己添加为通知中心的观察者UIApplicationDidReceiveMemoryWarningNotification

  2. 确保您的缓存图像是缩略图大小,否则您的 UI 中总会出现一点点卡顿(如果您需要调整大小算法,请告诉我们),这会不必要地占用内存;

  3. 当您将图像更新发送回主队列时,您应该异步执行此操作(为什么该后台队列在等待将块发送回主队列完成时会挂起并占用资源......这个一旦您在快速滚动期间备份了几张图像,这尤其是一个问题);和

  4. 当您分派回主队列时,您应该检查以确保您从中获取的单元格cellForRowAtIndexPath不是nil(因为如果单元格加载逻辑得到太多备份(尤其是在较慢的设备上),理论上您可以让有问题的单元格滚动关闭屏幕和您的算法可能会崩溃)。

我使用的算法与您的算法非常相似,具有几乎相同的 GCD 结构(带有上述注意事项)并且滚动非常流畅,即使在旧设备上也是如此。如果您希望我发布代码,我很乐意。

如果您仍然遇到问题,CPU 分析器非常适合识别瓶颈并让您知道应该将注意力集中在哪里。网上有一些很棒的 WWDC 会议,它们专注于如何使用 Instruments 来识别性能瓶颈,我发现它们对于熟练使用 Instruments 非常有帮助。

这是我的代码。在viewDidLoad中,我初始化我的图像缓存:

- (void)initializeCache
{
    self.imageCache = [[NSCache alloc] init];
    self.imageCache.name = @"Custom Image Cache";
    self.imageCache.countLimit = 50;
}

然后我在我的tableView:cellForRowAtIndexPath

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

    // set the various cell properties

    // now update the cell image

    NSString *imagename = [self imageFilename:indexPath]; // the name of the image being retrieved

    UIImage *image = [self.imageCache objectForKey:imagename];

    if (image)
    {
        // if we have an cachedImage sitting in memory already, then use it

        cell.imageView.image = image;
    }
    else
    {
        cell.imageView.image = [UIView imageNamed:@"blank_image.png"];

        // the get the image in the background

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

            // get the UIImage

            UIImage *image = [self getImage:imagename];

            // if we found it, then update UI

            if (image)
            {
                dispatch_async(dispatch_get_main_queue(), ^{

                    // if the cell is visible, then set the image

                    UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
                    if (cell)
                        cell.imageView.image = image;

                    [self.imageCache setObject:image forKey:imagename];
                });
            }
        });
    }

    return cell;
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];

    [self.imageCache removeAllObjects];
}

顺便说一句,您可能考虑的进一步优化是将缓存的图像预加载到单独的队列中,而不是在单独的线程中即时加载图像。我认为没有必要,因为这对我来说似乎已经足够快了,但它是加快 UI 速度的另一种选择。

于 2012-07-16T06:14:02.203 回答
1

缺少的步骤是使用获取的图像更新模型。实际上,您每次都在为每个单元进行新的加载。该模型是缓存相对昂贵的负载结果的正确位置。你可以添加一个 Beer.image 属性吗?

然后,您的配置代码将如下所示:

Beer *beer = (Beer *) [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.displayBeerName.text = beer.name;

// did we already cache a copy of the image?
if (beer.image != nil) {
    // good.  use it.  this will run quick and this will run most of the time
    cell.beerImage.image = beer.image;
} else {
    // it must be the first time we've scrolled by this beer.  do the expensive
    // image init off the main thread

    cell.beerImage.image  = nil;   // set a default value here.  nil is good enough for now

    [self loadImageForBeer:beer atIndexPath:indexPath];
}

为清楚起见,将加载程序逻辑移至此处...

- (void)loadImageForBeer:(Beer *)beer atIndexPath:(NSIndexPath *)indexPath {

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
    dispatch_async(queue, ^{

        UIImage *image = [UIImage imageWithContentsOfFile:beer.imagePath];
        beer.image = image;

        dispatch_sync(dispatch_get_main_queue(), ^{
            UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
            cell.beerImage.image = image;
        });
    });
}
于 2012-07-14T20:57:08.223 回答
1

对于初始负载,您在这里可以做的不多,您的速度差不多。如果它仍然太慢,请尝试加载较小的图像,如果可以的话。

有几件事:

首先,小心使用 -imageWithContentsOfFile,它不会缓存任何内容。每次加载图像时都会受到全部打击,而不是 -imageNamed 会在某些缓存中保持图像温暖。您当然可以将其缓存在您的域对象中,但我个人强烈建议不要这样做。你的内存占用会飞涨,迫使你实现自己的缓存过期机制,而苹果通过 -imageNamed 有一个非常好的图像缓存。如果你能在所有 3 个系列的设备上做得比苹果更好,我会感到惊讶 :)

然后,您在这里打破了 UITableView 的享元模式:

dispatch_sync(dispatch_get_main_queue(), ^{
            cell.beerImage.image = image;
            beer.image = image;
            [cell setNeedsLayout];
        });

要求表格视图在给定索引处提供单元格,而不是捕获块中的单元格:在加载图像时,该单元格实例实际上可能已被重用于另一个索引路径,并且您将显示图像在错误的单元格中。

而且这里不需要 -setNeedsLayout ,只需要改变图像就足够了。

编辑:哎呀!我错过了表格视图中图像的明显之处。你的图片有多大,图片视图有多大,图片上的内容模式是什么?如果您的图像与图像视图的大小非常不同,并且您要求图像视图调整大小,这将发生在主线程上,并且您将在那里受到巨大的性能影响。加载后,将图像大小调整为关闭线程的图像视图(快速谷歌搜索将为您提供执行此操作的核心图形代码)。

于 2012-07-15T03:36:26.853 回答
-1

你可以看看这个问题,以前在堆栈溢出时回答过。

uitableViewcell 中的 UIImage 减慢滚动表

或者试试这个代码

- (void)configureCell:(BeerCell *)cell 
          atIndexPath:(NSIndexPath *)indexPath 
{
    Beer *beer = (Beer *) [self.fetchedResultsController objectAtIndexPath:indexPath];
    cell.displayBeerName.text = beer.name;

           UIImage *image = [UIImage imageWithContentsOfFile:beer.imagePath];

            cell.beerImage.image = image;
            [cell setNeedsLayout];
        }
于 2012-07-14T19:45:26.973 回答