表视图滚动asynchronous

我将图像加载到表格视图单元格,每个单元格都有一个图像。 我已经适应了几个教程到下面的代码,但我仍然有减速。

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

修改代码:

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; }); }); } 

你的algorithm看起来不错。 你避免了许多典型的陷阱。 如果你仍然有UI性能问题,我会build议一些事情:

  1. 你应该尝试caching你的图像在内存中。 你可以使用NSMutableArrayNSMutableDictionary ,但在最佳的方式caching图像在iOS应用程序? Caleb讨论了NSCache类的优点,简化了这个过程。 如果您要caching图像,请确保响应内存压力并在必要时清除caching。 您可以响应didReceiveMemoryWarning或将自己添加为通知中心的UIApplicationDidReceiveMemoryWarningNotification的观察者。

  2. 确保你的caching图像是缩略图的大小,否则你的UI总是会有一些口吃(如果你需要一个resize的algorithm,让我们知道),它会不必要地占用内存;

  3. 当你把图像更新发送回主队列时,你应该这样做是asynchronous的(为什么有这个后台队列挂起,并等待块被发送回主队列完成…这一旦你在快速滚动中备份了一些图像,这个问题尤其会成为一个问题)。 和

  4. 当你回到主队列时,你应该检查以确保你从cellForRowAtIndexPath得到的单元格不是nil (因为如果单元cellForRowAtIndexPath载逻辑得到了太多的备份(尤其是在较慢的设备上),理论上你可以让单元格滚动closures屏幕,你的algorithm可能会崩溃)。

我使用非常像你的algorithm,几乎相同的GCD结构(有上述注意事项),即使在较旧的设备上,也非常平滑的滚动。 如果你想让我发布代码,我很高兴。

如果你仍然有麻烦,CPU分析器是非常好的识别瓶颈,并让你知道你应该把注意力集中在哪里。 WWDC有一些很棒的在线会议,主要关注如何使用Instruments来识别性能瓶颈,并且我发现它们对于仪器的熟练程度非常有帮助。

这是我的代码。 在viewDidLoad ,我初始化了我的图像caching:

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

另外,您可能会考虑的另一个优化方法是将caching的图像预加载到单独的队列中,而不是在适时的单独线程中加载图像。 我不认为这是必要的,因为这似乎对我来说已经足够快了,但是这是加速用户界面的又一个select。

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

几件事情:

首先,要小心-imageWithContentsOfFile,它不会caching任何东西。 每当您加载图像时,您都会受到全面打击,而不是-imageNamed,这会使图像在某个caching中保持温暖。 你当然可以caching在你的域对象,但我个人强烈反对的build议。 你的内存足迹将会穿越屋顶,迫使你实现你自己的caching过期机制,而苹果有一个非常好的图像caching通过-imageNamed。 我会惊讶,如果你可以做比所有3家庭的设备上的苹果更好的工作:)

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

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

请问表视图给你的单元格在一个给定的索引,而不是捕获块中的单元格:到图像加载时,该单元格实例可能实际上已经被重新用于另一个索引path,你会显示图像在错误的单元格中。

这里不需要-setNeedsLayout,只是改变图像就足够了。

编辑:哎呀! 我错过了表格视图中的图像显而易见的事情。 你的图片尺寸是多less,图片浏览的尺寸是多less,图片上的内容模式是什么? 如果您的图片的大小与图片视图大小不同,并且您要求imageviewresize,则会在主线程中发生这种情况,您将在此处获得大量的性能。 加载后(快速谷歌search将给你的核心graphics代码来做到这一点)的图像resize的图像查看closures线程。

缺less的步骤是用获取的图像更新模型。 就这样,你每次都在为每个单元格做一个新的加载。 该模型是caching相对昂贵的负载的结果的正确的地方。 你能添加一个Beer.image属性吗?

然后,你的configuration代码看起来像这样:

 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; }); }); } 

你可以看看这个问题,以前在堆栈溢出回答。

在uitableViewcell中的UIImage减慢滚动表

否则试试这个代码

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