虽然我最初的答案(如下)试图解决与异步图像检索相关的几个关键问题,但它仍然从根本上受到限制。正确的实现还可以确保,如果您快速滚动,可见单元格的优先级高于滚动到屏幕外的单元格。它还支持取消先前的请求(并在适当的时候为您取消它们)。
虽然我们可以将这些功能添加到下面的代码中,但最好采用一个成熟的、经过验证的解决方案,该解决方案利用NSOperationQueue
and NSCache
下面讨论的技术,还解决了上述问题。最简单的解决方案是采用一种既定的解决方案UIImageView
支持异步图像检索的类别。这AF网络 https://github.com/AFNetworking/AFNetworking and SDWeb图像 https://github.com/rs/SDWebImage图书馆都有UIImageView
优雅地处理所有这些问题的类别。
您可以使用NSOperationQueue https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/NSOperationQueue_class/Reference/Reference.html or GCD http://developer.apple.com/library/ios/#documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html进行延迟加载(请参阅并发编程指南 http://developer.apple.com/library/ios/#documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008091用于讨论不同的异步操作技术)。前者的优点是您可以精确指定允许的并发操作数量,这对于从 Web 加载图像非常重要,因为许多 Web 服务器限制它们从给定客户端接受的并发请求数量。
基本思想是:
- 在单独的后台队列中提交图像数据的请求;
- 下载完图像后,将 UI 更新分派回主队列,因为您永远不应该在后台进行 UI 更新;
- 在主队列上运行分派的最终 UI 更新代码时,请确保
UITableViewCell
仍然可见,并且它尚未出队并重新使用,因为相关单元格已滚动到屏幕之外。如果您不这样做,可能会暂时显示错误的图像。
您可能希望将您的代码替换为类似以下代码的代码:
首先,为您定义一个属性NSOperationQueue https://developer.apple.com/library/ios/#documentation/Cocoa/Reference/NSOperationQueue_class/Reference/Reference.html您将用于下载图像,以及NSCache https://developer.apple.com/library/ios/#documentation/Cocoa/Reference/NSCache_Class/Reference/Reference.html用于存储这些图像:
@property (nonatomic, strong) NSOperationQueue *imageDownloadingQueue;
@property (nonatomic, strong) NSCache *imageCache;
其次,初始化这个队列并缓存在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
可能看起来像:
- (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
本身不再优雅地响应低内存情况,因此您确实应该为此通知添加观察者并在低内存情况下清空缓存。
我可能会建议一堆其他优化(例如,也许还将图像缓存到持久存储中以简化未来的操作;实际上我将所有这些逻辑放在我自己的逻辑中)AsyncImage
类),但首先看看这是否解决了基本的性能问题。