带有图像的 UITableView 滚动非常缓慢 [英] UITableView with images scrolls very slowly
问题描述
可能的重复:
带有图像的表格视图,缓慢加载和滚动
我有一个 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).
虽然我们可以将这些类型的功能添加到下面的代码中,但最好采用一个成熟的、经过验证的解决方案,该解决方案利用下面讨论的 NSOperationQueue
和 NSCache
技术,而且还解决了上述问题.最简单的解决方案是采用支持异步图像检索的已建立的 UIImageView
类别之一.AFNetworking 和 SDWebImage 库都有 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.
您可以使用 NSOperationQueue
或 GCD 进行延迟加载(参见 并发编程指南,用于讨论不同的异步操作技术).前者的优势在于您可以精确指定允许的并发操作数,这对于从 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.
基本思想是:
- 在单独的后台队列中提交图像数据请求;
- 完成下载图像后,将 UI 更新分派回主队列,因为您永远不应该在后台进行 UI 更新;
- 在主队列上运行分派的最终 UI 更新代码时,确保
UITableViewCell
仍然可见,并且它没有出列和重用,因为有问题的单元格滚出屏幕.如果不这样做,可能会暂时显示错误的图像.
- Submit request of the image data in a separate background queue;
- When done downloading image, dispatch UI update back to main queue because you should never do UI updates in the background;
- 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 Actually, NSCache
doesn't respond UIApplicationDidReceiveMemoryWarningNotification
, but during actual operation, when memory is low, the cache will be purged.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屋!