表视图滚动异步

时间:2012-07-14 19:35:48

标签: iphone objective-c ios

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

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

修改修改后的代码:

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 个答案:

答案 0 :(得分:12)

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

  1. 您应该尝试在内存中缓存图像。您可以使用NSMutableArrayNSMutableDictionary,但在Best way to cache images on ios app? Caleb讨论了NSCache类的优点,这简化了流程。如果您确实缓存了图像,请确保响应内存压力并在必要时清除缓存。您可以回复didReceiveMemoryWarning或将自己添加为通知中心UIApplicationDidReceiveMemoryWarningNotification的观察员。

  2. 确保您的缓存图片是缩略图大小的,否则您的UI中总会有一点口吃(如果您需要调整大小算法,请告诉我们),它会不必要地耗尽内存;

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

  4. 当您调度回主队列时,应检查以确保从cellForRowAtIndexPath获得的单元格不是nil(因为如果单元格加载逻辑过于备份(尤其是较慢的设备),理论上你可以让有问题的单元滚出屏幕而你的算法可能崩溃了。

  5. 我使用的算法与您的算法非常相似,具有几乎相同的GCD结构(具有上述警告)并且即使在较旧的设备上也非常平滑滚动。如果你想让我发布代码,我很高兴。

    如果您仍然遇到麻烦,那么CPU分析器非常适合识别瓶颈并让您知道应该集中注意力的位置。在线提供了一些很棒的WWDC会议,重点是如何使用仪器来识别性能瓶颈,我发现它们对于熟练使用仪器非常有帮助。

    这是我的代码。在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];
    }
    

    另外,您可能考虑的另一个优化是将缓存的图像预加载到单独的队列中,而不是在单独的线程中及时加载图像。我认为这不是必要的,因为这似乎对我来说足够快,但它是加速用户界面的另一个选择。

答案 1 :(得分: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;
        });
    });
}

答案 2 :(得分:1)

你可以在这里为初始负载做多少,你的速度和它一样快。 如果它仍然太慢,请尝试加载较小的图像。

有几件事:

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

然后,你在这里打破了UITableView的flyweight模式:

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

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

这里不需要-setNeedsLayout,只需更改图像即可。

编辑:哎呀!我在桌面视图中错过了图像的显而易见的事情。您的图像尺寸,图像视图的大小以及图像的内容模式是什么? 如果您的图像大小与图像视图的大小差别很大,并且您要求图像视图调整大小,则会在主线程上发生这种情况,并且您将在那里进行大量性能搜索。 加载后,将图像大小调整为线程外的图像视图(快速谷歌搜索将为您提供核心图形代码)。

答案 3 :(得分:-1)

您可以查看此问题,之前已在堆栈溢出处回答。

UIImage in uitableViewcell slowdowns scrolling table

或尝试此代码

- (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];
        }