如何为tableview缓存数据?

8
我有一个表视图,其中包含填充单元格的大型图像,并且行高基于图像大小设置。不幸的是,当滚动到下一个单元格时,表会出现明显的抖动。
我被告知,如果在将它们加载到表中之前缓存行高和图像,我的表视图将滚动得更流畅。
所有数据都存储在plist中。
如何进行缓存操作? 代码是什么样的?它应该放到哪里?
谢谢!
以下是我用于加载图像的代码:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    static NSString *detailTableViewCellIdentifier = @"Cell";

    DetailTableViewCell *cell = (DetailTableViewCell *) 
        [tableView dequeueReusableCellWithIdentifier:detailTableViewCellIdentifier];

    NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"DetailTableViewCell" owner:self options:nil];
    for(id currentObject in nib)
    {
        cell = (DetailTableViewCell *)currentObject;
    }
    AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
    NSString *Path = [[NSBundle mainBundle] bundlePath];
    NSString *MainImagePath = [Path stringByAppendingPathComponent:([[appDelegate.sectionsDelegateDict objectAtIndex:indexPath.section] objectForKey:@"MainImage"])];

    cell.mainImage.image = [UIImage imageWithContentsOfFile:MainImagePath];

    return cell;
}

我还使用以下方法来计算行高:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    AppDelegate *appDelegate = (DrillDownAppAppDelegate *)[[UIApplication sharedApplication] delegate];
    NSString *Path = [[NSBundle mainBundle] bundlePath];
    NSString *MainImagePath = [Path stringByAppendingPathComponent:([[appDelegate.sectionsDelegateDict objectAtIndex:indexPath.section] objectForKey:@"MainImage"])];
    UIImage *imageForHeight = [UIImage  imageWithContentsOfFile:MainImagePath]; 
    imageHeight = CGImageGetHeight(imageForHeight.CGImage);  
    return imageHeight;
}

编辑:以下是最终代码。

#define PHOTO_TAG 1
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
 static NSString *CellIdentifier = @"Photo";

 UIImageView *photo;
 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

 AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
 UIImage *theImage = [UIImage imageNamed:[[appDelegate.sectionsDelegateDict objectAtIndex:indexPath.section] objectForKey:@"MainImage"]];

 imageHeight = CGImageGetHeight(theImage.CGImage);
 imageWidth = CGImageGetWidth(theImage.CGImage);

if (cell == nil) {
    cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
    photo = [[[UIImageView alloc] initWithFrame:CGRectMake(0, 0, imageWidth, imageHeight)] autorelease];
    photo.tag = PHOTO_TAG;
    [cell addSubview:photo];
   } else {
    photo = (UIImageView *) [cell viewWithTag:PHOTO_TAG];
    [photo setFrame:CGRectMake(0, 0, imageWidth, imageHeight)];
   }

 photo.image = theImage;
 return cell;
 }
2个回答

16
缓存并不是UITableView性能的万能解决方案。 只有当有昂贵的计算任务且可以避免重复计算时,缓存才是有价值的。 如果您的UITableViewCell中有太多视图,则无论如何缓存都对您没有帮助。 如果行高全部相同,则没有可缓存的内容。 如果使用 + [UIImage imageNamed:] ,则系统已经为您缓存了图像。
UITableViewCells的最常见的第一级问题是在其中放置过多的子视图。 您如何构造单元格? 您花时间研究Table View编程指南,特别是A Closer Look at Table-View Cells吗? 理解此文档将在以后节省大量烦恼。
编辑:(基于上面的代码)
首先,您正在获取可重用单元格,然后立即将其丢弃,读取NIB并迭代所有顶级对象以查找单元格(几乎看起来与您刚才丢弃的单元格完全相同)。 然后您工作出一个字符串,用于打开文件并读取其内容。 这是每当UITableView需要新单元格时都会执行的操作,这是很多次。 对于相同的行,您一遍又一遍地执行相同的操作。
然后,当UITableView想要知道高度时,您再次从磁盘读取图像。 每次UITableView询问时都会这样做(尽管它会尝试优化此过程)。
您应该首先阅读我上面链接的UITableView编程指南。 希望这会有所帮助。 当您完成后,这里是您应该考虑的事情:
  • 您表示这个单元格中只有一个图像视图。您真的需要为此使用NIB吗?如果您坚持使用NIB(在某些情况下确实有理由使用它们),那么请阅读编程指南,了解如何实现基于NIB的单元格。您应该使用IBOutlet,而不是尝试迭代顶层对象。

  • +[UIImage imageNamed:]将自动查找资源目录中的文件,而无需您计算出包的路径。它还会为您自动缓存这些图像。

  • -dequeueReusableCellWithIdentifier:的目的是获取UITableView不再使用的单元格,并且您可以重新配置它,而不是创建新的单元格。您正在调用它,但立即将其丢弃。您应该检查它是否返回了nil,并且仅在返回了nil时才从NIB中加载它。否则,您只需要更改图像。同样,请阅读编程指南;它有许多这方面的例子。只要确保您真正尝试理解-dequeueReusableCellWithIdentifier:正在做什么,并且不要将其视为程序中的某个点上的东西。


Rob,感谢您的回复。我只有一个单一图像视图。我从之前提出的问题中得到了缓存图像的想法:https://dev59.com/JHM_5IYBdhLWcg3wgjW2 - Jonah
rpetrich的回答很好。关于缓存rowHeight,最简单的解决方案是维护一个NSNumber数组,用于存储每一行计算出来的高度。你可以先将它们全部设为0,如果为0,则计算并将结果存储在数组中。如果不为0,则直接返回。在进行太多随机更改之前,请使用Instruments进行一些分析,并查看您真正花费时间的地方。行高计算可能是您问题中最小的一部分。也许您经常缩放图像(这似乎是一个可能性)。Instruments会有所帮助。 - Rob Napier
哦,哇...是的,那会非常慢...你目前正在绕过iPhone提供的每个优化:D我需要编辑我的上面的答案以提供更好的细节。我很惊讶它只有有时会有点卡顿。哇。那做了很多工作。 - Rob Napier
请看我的最终代码,它运行得很好。谢谢你的帮助! - Jonah
@RobNapier 非常感谢您,您救了我一天。 - Doan Cuong
显示剩余2条评论

5

如果您确实需要缓存高度,可以像我这样做(缓存一个显示“文章”对象的单元格的高度 - 文章可能是几个子类之一):

+ (CGFloat) heightForArticle: (Article*) article atWidth: (CGFloat) width {

    static NSCache* heightCache = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        heightCache = [NSCache new];
    });

    NSAssert(heightCache, @"Height cache must exist");

    NSString* key = @"unique"; //Create a unique key here
    NSNumber* cachedValue = [heightCache objectForKey: key];
    if( cachedValue ) 
        return [cachedValue floatValue];
    else {
        CGFloat height = 40;//Perform presumably large height calculation here

        [heightCache setObject: [NSNumber numberWithFloat: height] forKey: key];

        return height;
    }
}

这真的帮了我很多,我用它来缓存高度,也用它来缓存我的表视图单元格图片。谢谢 :) - Supertecnoboff
实际上,我很抱歉,但我撤回我的评论。这个不起作用,我注意到有时它会返回错误的高度值。因此,有时单元格会变得太大或太小。 - Supertecnoboff

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接