如何清除缓存的 UITableViewCell
Posted
技术标签:
【中文标题】如何清除缓存的 UITableViewCell【英文标题】:How to purge a cached UITableViewCell 【发布时间】:2010-02-18 06:13:37 【问题描述】:有人对如何清除缓存的UITableViewCell
有建议吗?
我想用重用标识符缓存这些单元格。但是,有时我需要删除或修改某些表行。我希望在行更改后致电reloadData
。
现在,dequeueReusableCellWithIdentifier
总是返回之前缓存的(过时的)条目。如何指示缓存已过时且需要清除?
【问题讨论】:
【参考方案1】:我不确定您为什么要首先清除单元格。每次使单元格出列时,都需要重新设置任何需要显示的数据。缓存只是防止您每次都必须设置任何不变的属性。但必须设置正在显示的实际数据,即使单元格之前已缓存。
请注意,对于同一类型的所有单元格,您的重用标识符应该相同。如果您正在做一些愚蠢的事情,例如根据相关行计算标识符,那么您做错了。
您的代码应该类似于
- (UITableViewCell *)tableView:(UITableView *)view cellForRowAtIndexPath:(NSIndexPath *)indexPath NSString *identifier = @"CellIdentifier"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier]; 如果(!单元格) // 没有缓存单元格,在这里新建一个 cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier] autorelease]; // 设置所有此类单元格应共享的任何属性,例如附件或文本颜色 // 为这个特定的单元格设置数据 cell.textLabel.text = @"foo"; 返回单元格;在该示例中,请注意我如何始终为单元格设置数据,并且所有单元格共享相同的标识符。如果您遵循此模式,则根本没有理由尝试“清除”您的单元格,因为任何旧数据都会被覆盖。如果您有多种类型的单元格,您可能希望在这种情况下使用多个标识符,但每个单元格类型仍然是 1 个标识符。
【讨论】:
对每一行使用不同的标识符很容易在每个表格视图单元格上异步加载远程缩略图。否则,单元格可能会加载之前加载的其他行中显示的缩略图。如果你有一个优雅地处理这种情况的建议,我想知道。 @Jesse Armand:为每一行使用不同的标识符是泄漏每个单元格的好方法。当然,这些不是“真正的”泄漏,因为一旦表本身发生泄漏,它们就会消失,但是您正在积极缓存永远不会再次使用的对象。您只需要更聪明地在重复使用单元格时取消缩略图加载,或者在您的缩略图准备好显示时检查该单元格是否仍然代表相同的对象。 @Yar:如果您没有在单元格之间共享您的标识符,那么您就是在故意“泄漏”您创建的每个单元格(我把它放在引号中,因为只要表格是活)。如果您不使用dequeue
,那么您再次“泄漏”您创建的每个单元格。您不是在保存 one alloc/init,而是在保存 EVERY SINGLE alloc/init 超过前 7 个(左右,取决于单元格高度和表格高度,基本上只是numCellsVisible + 2
)。
感谢@KevinBallard,抱歉我删除了我的评论,但您的回复是独立的。非常感谢。我终于明白了这个话题以及我一直以来做错了什么。
@DanRosenstark 没错。 UITableView
对可重用单元进行排队,因此一旦 UITableView
解除分配,队列就会被释放,因此所有缓存的单元也会被释放。【参考方案2】:
我不知道如何清除缓存,但是,当需要更改行时,我使用了一种解决方法来处理这种情况。
首先,我不使用静态单元格标识符。我要求数据对象生成它(注意[myObject signature]
,它提供了一个描述所有需要属性的唯一字符串):
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
MyObject *myObject = _dataSource[indexPath.row];
NSString *cellId = [myObject signature];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
if (!cell)
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId];
[myObject configureCell:cell];
[myObject updateCellWithData:cell];
return cell;
[myObject signature]
为不同的属性列表提供不同的签名。所以,如果我的对象发生了变化,我只需调用[self.tableView reloadData]
,myObject 会提供一个新的签名,表格会为其加载一个新的单元格。
[myObject configureCell:cell]
将所有需要的子视图放置到单元格中。
[myObject updateCellWithData:cell]
用当前数据更新单元格的子视图。
【讨论】:
这是个好主意。不幸的是,它不适用于从 XIB 加载的单元格,因为 cellForRowAtIndexPath 中使用的标识符必须与 XIB 中设置的标识符匹配。【参考方案3】:while ([tableView dequeueReusableCellWithIdentifier:@"reuseid"])
【讨论】:
这是一个很好的解决方案。当你出队时,你传递一个重用 id。如果您更改/增加重用 ID,它将生成一个新单元格。据推测,ios 将在/如果需要它来管理内存时垃圾收集旧单元。【参考方案4】:我找到了一个非常好的方法。
在.h中
//add this
int reloadCells;
在.m中
- (void)dumpCache
reloadCells = 0;
[tableView reloadData];
-(void)tableView:(UITableView *)tableView cellForRowAtUndexPath:(NSIndexPath *)indexPath
static NSString *CellID = @"Cell"
UITableViewCell *cell = [tableView dequeCellWithReuseIdentifier:CellID];
if (cell == nil || reloadCells < 12)
cell = [[UITableViewCell alloc] initWithFormat:UITableViewCellStyleDefault reuseIdentifier:CellID];
reloadCells ++;
cell.textLabel.text = @"My Cell";
return cell;
【讨论】:
我认为这个人不喜欢常数“12”。您必须选择一个大于每个都需要缓存的单元格的最大数量的值——这可能是一个猜测。但是,解决方案非常简单,而且有效!所以我要给它一个赞成票。 在这种情况下(void) dumpCache
如何被调用?
当您需要重新制作所有单元格时,您会调用 dumpCache。它将使用 [self dumpCache]; 调用【参考方案5】:
我今天不得不做类似的事情。我有一个图像库,当用户将它们全部删除时,库需要摆脱最后一个排队的单元格并将其替换为“画廊空”图像。当删除例程完成时,它会将“清除”标志设置为 YES,然后执行 [tableView reloadData]。 cellForRowAtIndexPath 中的代码如下所示:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
NSString *cellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: cellIdentifier];
if (purge)
cell = nil;
purge = NO;
if (cell == nil)
//*** Do usual cell stuff
return cell
【讨论】:
这对我有用——唯一的区别是我必须遍历所有单元格然后设置 purge=NO。我补充说: if (([indexPath row]+1)==(listOfItems.count)) self.purge=0; 就在返回单元格之前。【参考方案6】:我正在编写 ObjC(手动引用计数)代码,发现一些奇怪的行为会阻止 UITableView 和 UITableViewCells 被释放,尽管我的内存管理是正确的。 UITableView 和 UITableViewCell 似乎相互保留。是的,我找到了一种强制 UITableView 及其单元格相互释放的方法(删除缓存的单元格)。
要做到这一点,基本上你需要一个表格视图来获得一个可重用的单元格。表格视图将从其可重用单元缓存中删除它。毕竟,您告诉该单元格从其超级视图中删除。完成。
现在作为一个独立的类...首先,您需要一个包含您使用的所有单元格标识符的数组。接下来,您实现一个虚拟数据源并让该数据源通过使用“ClearAllCachedCells”数据源重新加载自身来清除 UITableView:
#import <UIKit/UIKit.h>
@interface ClearAllCachedUITableViewCellsDataSource : NSObject <UITableViewDataSource, UITableViewDelegate>
+(void)clearTableView:(UITableView*)tv
reuseIdentifiers:(NSArray<NSString*>*)cellIdentifiers
delegateAfterFinish:(id<UITableViewDelegate>)dg
dataSourceAfterFinish:(id<UITableViewDataSource>)ds;
@end
魔法发生在 .m 文件中:
#import "ClearAllCachedUITableViewCellsDataSource.h"
@interface ClearAllCachedUITableViewCellsDataSource ()
BOOL clearing;
@property (nonatomic, readonly) UITableView *tv;
@property (nonatomic, readonly) NSArray<NSString*> *identifiers;
@property (nonatomic, readonly) id ds;
@property (nonatomic, readonly) id dg;
@end
@implementation ClearAllCachedUITableViewCellsDataSource
-(void)dealloc
NSLog(@"ClearAllCachedUITableViewCellsDataSource (%i) finished", (int)_tv);
[_tv release];
[_ds release];
[_dg release];
[_identifiers release];
[super dealloc];
-(NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section
[self performSelectorOnMainThread:@selector(clear) withObject:nil waitUntilDone:NO];
NSLog(@"TV (%i): reloading with zero cells", (int)_tv);
return 0;
-(void)clear
if (!clearing)
clearing = YES;
for (NSString *ident in self.identifiers)
UITableViewCell *cell = [_tv dequeueReusableCellWithIdentifier:ident];
while (cell)
NSLog(@"TV (%i): removing cached cell %@/%i", (int)_tv, ident, (int)cell);
[cell removeFromSuperview];
cell = [_tv dequeueReusableCellWithIdentifier:ident];
self.tv.delegate = self.tv.delegate == self ? self.dg : self.tv.delegate;
self.tv.dataSource = self.tv.dataSource == self ? self.ds : self.tv.dataSource;
[self release];
+(void)clearTableView:(UITableView*)tv
reuseIdentifiers:(NSArray<NSString*>*)cellIdentifiers
delegateAfterFinish:(id<UITableViewDelegate>)dg
dataSourceAfterFinish:(id<UITableViewDataSource>)ds
if (tv && cellIdentifiers)
NSLog(@"TV (%i): adding request to clear table view", (int)tv);
ClearAllCachedUITableViewCellsDataSource *cds = [ClearAllCachedUITableViewCellsDataSource new];
cds->_identifiers = [cellIdentifiers retain];
cds->_dg = [dg retain];
cds->_ds = [ds retain];
cds->_tv = [tv retain];
cds->clearing = NO;
tv.dataSource = cds;
tv.delegate = cds;
[tv performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO];
@end
【讨论】:
【参考方案7】:应使用消息prepareForReuse
清除先前使用单元格的旧数据。当单元格出列时,此消息会在从dequeueReusableCellWithIdentifier:
返回之前发送到该单元格。
【讨论】:
好的,谢谢。我希望找到一种方法使整个缓存一举失效。但是,这种解决方案(手动清除数据以使缓存失效并进行失效,一次一行)也应该可以工作 我认为你不明白 Giao 的回答。他根本没有建议你做什么。prepareForReuse:
方法会在每个单元格出列时发送到每个单元格,然后再交给您。这是自动发生的事情,只有在实现自己的 UITableViewCell
子类时才关心它。【参考方案8】:
没办法,我觉得…… 我使用了完全不同的方法。我没有依赖 UITableView 缓存,而是自己构建。通过这种方式,我可以完美地控制何时以及清除什么。 像这样的...
给定:
NSMutableDictionary *_reusableCells;
_reusableCells = [NSMutableDictionary dictionary];
我能做到:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
NSString *cellIdentifier = @"whatever-you-need";
UITableViewCell *cell = [_reusableCells objectForKey:cellIdentifier];
if (cell == nil)
// create a new cell WITHOUT reuse-identifier !!
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
// store it in my own cache
[_reusableCells setObject:cell forKey:cellIdentifier];
/* ...configure the cell... */
return cell;
和:
-(void)didReceiveMemoryWarning
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
[_reusableCells removeAllObjects];
希望这会有所帮助。
【讨论】:
以上是关于如何清除缓存的 UITableViewCell的主要内容,如果未能解决你的问题,请参考以下文章