我正在将图像加载到表格视图单元格中,每个单元格都有一个图像。我在下面的代码中添加了几个教程,但我仍然在放慢速度。
我正在从文档目录加载这些图像。关于如何加快这一过程的任何提示或想法?
修改修改后的代码:
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;
});
});
}
答案 0 :(得分:12)
你的算法看起来很不错。你已经避免了许多典型的陷阱。如果你仍然遇到UI性能问题,我建议你做几件事:
您应该尝试在内存中缓存图像。您可以使用NSMutableArray
或NSMutableDictionary
,但在Best way to cache images on ios app? Caleb讨论了NSCache
类的优点,这简化了流程。如果您确实缓存了图像,请确保响应内存压力并在必要时清除缓存。您可以回复didReceiveMemoryWarning
或将自己添加为通知中心UIApplicationDidReceiveMemoryWarningNotification
的观察员。
确保您的缓存图片是缩略图大小的,否则您的UI中总会有一点口吃(如果您需要调整大小算法,请告诉我们),它会不必要地耗尽内存;
当您将映像更新发送回主队列时,应该异步执行此操作(为什么在等待将块发送回主队列时,该后台队列会挂起并占用资源完成...一旦你在快速滚动期间备份了几张图像,这尤其是一个问题;和
当您调度回主队列时,应检查以确保从cellForRowAtIndexPath
获得的单元格不是nil
(因为如果单元格加载逻辑过于备份(尤其是较慢的设备),理论上你可以让有问题的单元滚出屏幕而你的算法可能崩溃了。
我使用的算法与您的算法非常相似,具有几乎相同的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];
}