带有图像的 UITableView 滚动非常缓慢 [英] UITableView with images scrolls very slowly

查看:35
本文介绍了带有图像的 UITableView 滚动非常缓慢的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

可能的重复:
带有图像的表格视图,缓慢加载和滚动

我有一个 UITableView,它从服务器下载 UITableViewCells 的图像.我观察到 tableView 滚动得很慢.

I have a UITableView which downloads images for the UITableViewCells from a server. I observed that the tableView scrolls very slowly.

我认为这可能是下载问题,但我发现下载完成后表格仍然滚动缓慢并且图像图标尺寸较小.

I thought that this might downloading problem, but I have realized that the table still scrolls slowly after download has finished and the image icon size is less.

我在 Google 上搜索过,但没有找到任何帮助.

I searched Google but couldn't find any help.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    btnBack.hidden = FALSE;

    static NSString *CellIdentifier = @"Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    if (cell == nil)
    {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
        cell.selectionStyle = UITableViewCellSelectionStyleNone;

        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
        cell.backgroundColor = [UIColor clearColor];

        cell.textLabel.font = [UIFont fontWithName:@"Noteworthy" size:17.0];
        cell.textLabel.font = [UIFont boldSystemFontOfSize:17.0];
        cell.textLabel.textColor = [UIColor blackColor];
        cell.textLabel.highlightedTextColor = [UIColor blackColor];
    }

        cell.textLabel.text = [NSString stringWithFormat:@"     %@", [test.arrTitle objectAtIndex:indexPath.row]];

        NSString *Path;
        Path = [NSString stringWithFormat:@"http://%@",[test.arrImages objectAtIndex:indexPath.row]];
        NSLog(@"image-->%@",[test.arrImages objectAtIndex:indexPath.row]);
        NSString *strImage = Path;
        NSURL *url4Image = [NSURL URLWithString:strImage];    
        NSData *data = [NSData dataWithContentsOfURL:url4Image];
        image =[[UIImage alloc] initWithData:data];
        cell.imageView.image =image;
        [image release];

        return cell;
}

推荐答案

虽然我在下面的原始答案试图解决与异步图像检索相关的几个关键问题,但它仍然从根本上受到限制.正确的实现还可以确保如果您快速滚动,可见单元格的优先级高于已滚动到屏幕外的单元格.它还支持取消先前的请求(并在适当的时候为您取消它们).

While my original answer, below, attempted to solve several key problems associated with asynchronous image retrieval, it is still fundamentally limited. A proper implementation would also make sure that if you scrolled quickly, that the visible cells were prioritized over cells that had scrolled off screen. It would also support cancelation of prior requests (and canceled them for you when appropriate).

虽然我们可以将这些类型的功能添加到下面的代码中,但最好采用一个成熟的、经过验证的解决方案,该解决方案利用下面讨论的 NSOperationQueueNSCache 技术,而且还解决了上述问题.最简单的解决方案是采用支持异步图像检索的已建立的 UIImageView 类别之一.AFNetworkingSDWebImage 库都有 UIImageView 类别,可以优雅地处理所有这些问题.

While we could add these sorts of capabilities to the below code, it is probably better to adopt an established, proven solution that utilizes the NSOperationQueue and NSCache technologies discussed below, but also addresses the above issues. The easiest solution is to adopt one of the established UIImageView categories that supports asynchronous image retrieval. The AFNetworking and SDWebImage libraries both have UIImageView categories that gracefully handle all of these issues.

您可以使用 NSOperationQueueGCD 进行延迟加载(参见 并发编程指南,用于讨论不同的异步操作技术).前者的优势在于您可以精确指定允许的并发操作数,这对于从 Web 加载图像非常重要,因为许多 Web 服务器限制了它们从给定客户端接受的并发请求数.

You can use NSOperationQueue or GCD to do your lazy loading (see Concurrency Programming Guide for discussion of different asynchronous operations technologies). The former enjoys an advantage that you can specify precisely how many concurrent operations are permissible, which is very important in loading images from the web because many web servers limit how many concurrent requests they will accept from a given client.

基本思想是:

  1. 在单独的后台队列中提交图像数据请求;
  2. 完成下载图像后,将 UI 更新分派回主队列,因为您永远不应该在后台进行 UI 更新;
  3. 在主队列上运行分派的最终 UI 更新代码时,确保 UITableViewCell 仍然可见,并且它没有出列和重用,因为有问题的单元格滚出屏幕.如果不这样做,可能会暂时显示错误的图像.
  1. Submit request of the image data in a separate background queue;
  2. When done downloading image, dispatch UI update back to main queue because you should never do UI updates in the background;
  3. When running dispatched final UI update code on main queue, make sure the UITableViewCell is still visible and that it hasn't been dequeued and reused because the cell in question scrolled off the screen. If you don't do that, the wrong image may momentarily show up.

您可能想用以下代码替换您的代码:

You would want to replace your code with something like the following code:

首先,为您的定义一个属性NSOperationQueue 用于下载图像,以及 NSCache 用于存储这些图像:

First, define a property for your NSOperationQueue that you will use for downloading images, as well as a NSCache for storing those images:

@property (nonatomic, strong) NSOperationQueue *imageDownloadingQueue;
@property (nonatomic, strong) NSCache *imageCache;

其次,在viewDidLoad中初始化这个队列并缓存:

Second, initialize this queue and cache in viewDidLoad:

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.imageDownloadingQueue = [[NSOperationQueue alloc] init];
    self.imageDownloadingQueue.maxConcurrentOperationCount = 4; // many servers limit how many concurrent requests they'll accept from a device, so make sure to set this accordingly

    self.imageCache = [[NSCache alloc] init];

    // the rest of your viewDidLoad
}

第三,您的 cellForRowAtIndexPath 可能如下所示:

Third, your cellForRowAtIndexPath might look like:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    btnBack.hidden = FALSE;

    static NSString *CellIdentifier = @"Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
        cell.selectionStyle = UITableViewCellSelectionStyleNone;

        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
        cell.backgroundColor = [UIColor clearColor];

        cell.textLabel.font = [UIFont fontWithName:@"Noteworthy" size:17.0];
        cell.textLabel.font = [UIFont boldSystemFontOfSize:17.0];
        cell.textLabel.textColor = [UIColor blackColor];
        cell.textLabel.highlightedTextColor = [UIColor blackColor];
    }

    cell.textLabel.text = [NSString stringWithFormat:@"     %@", [test.arrTitle objectAtIndex:indexPath.row]];

    // code change starts here ... initialize image and then do image loading in background

    NSString *imageUrlString = [NSString stringWithFormat:@"http://%@", [test.arrImages objectAtIndex:indexPath.row]];
    UIImage *cachedImage = [self.imageCache objectForKey:imageUrlString];
    if (cachedImage) {
        cell.imageView.image = cachedImage;
    } else {
        // you'll want to initialize the image with some blank image as a placeholder

        cell.imageView.image = [UIImage imageNamed:@"blankthumbnail.png"];

        // now download in the image in the background

        [self.imageDownloadingQueue addOperationWithBlock:^{

            NSURL *imageUrl   = [NSURL URLWithString:imageUrlString];    
            NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
            UIImage *image    = nil;
            if (imageData) 
                image = [UIImage imageWithData:imageData];

            if (image) {
                // add the image to your cache

                [self.imageCache setObject:image forKey:imageUrlString];

                // finally, update the user interface in the main queue

                [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                    // Make sure the cell is still visible

                    // Note, by using the same `indexPath`, this makes a fundamental
                    // assumption that you did not insert any rows in the intervening
                    // time. If this is not a valid assumption, make sure you go back
                    // to your model to identify the correct `indexPath`/`updateCell`

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

    return cell;
}

第四,也是最后一点,虽然人们可能倾向于在内存不足的情况下编写代码来清除缓存,但事实证明它会自动执行此操作,因此这里不需要额外的处理.如果你在模拟器中手动模拟内存不足的情况,你不会看到它驱逐它的对象,因为NSCache没有响应UIApplicationDidReceiveMemoryWarningNotification,但在实际操作中,当内存低,缓存将被清除. 实际上,NSCache 不再优雅地响应低内存情况本身,所以你真的应该为此通知添加观察者并在低内存情况下清空缓存.

Fourth, and finally, while one might be inclined to write code to purge the cache in low memory situations, it turns out that it does this automatically, so no extra handling is needed here. If you manually simulate a low memory situation in the simulator, you won't see it evict its objects because NSCache doesn't respond UIApplicationDidReceiveMemoryWarningNotification, but during actual operation, when memory is low, the cache will be purged. Actually, NSCache no longer gracefully responds to low memory situations itself, so you really should add observer for this notification and empty the cache in low memory situations.

我可能会建议一系列其他优化(例如,也许还将图像缓存到持久存储中以简化未来的操作;我实际上将所有这些逻辑放在我自己的 AsyncImage 类中),但首先看看是否这解决了基本的性能问题.

I might suggest a bunch of other optimizations (e.g. perhaps also caching images into persistent storage to streamline future operations; I actually put all of this logic in my own AsyncImage class), but first see if this solves the basic performance issue.

这篇关于带有图像的 UITableView 滚动非常缓慢的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆