可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Does anyone have suggestions on how to purge a cached UITableViewCell
?
I'd like to cache these cells with reuseIdentifier. However, there are times when I need to delete or modify some of the table rows. I expect to call reloadData
after the row changes.
Right now, dequeueReusableCellWithIdentifier
always returns the cached(obsolete) entry from before. How do I indicate that the cache is stale and needs to be purged?
回答1:
I'm not sure why you're trying to purge cells in the first place. Every time you dequeue a cell, you need to re-set any data that needs to be displayed. The caching just prevents you from having to set up any non-changing properties every time. But the actual data that's being displayed must be set, even if the cell was cached before.
Note that your reuse identifier is supposed to be the same for all the cells of the same type. If you're doing something silly like calculating the identifier based on the row in question, then you're doing it wrong.
Your code should look something like
- (UITableViewCell *)tableView:(UITableView *)view cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *identifier = @"CellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (!cell) {
// no cached cell, create a new one here
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier] autorelease];
// set any properties that all such cells should share, such as accessory, or text color
}
// set data for this particular cell
cell.textLabel.text = @"foo";
return cell;
}
In that example, note how I always set the data for the cell every single time, and all cells share the same identifier. If you follow this pattern, you should have no reason at all to try and "purge" your cells, as any old data will be overwritten anyway. If you have multiple types of cells, you may want to use multiple identifiers in that case, but it's still 1 identifier per cell type.
回答2:
I don't know how to purge the cache, however, I use a workaround how to handle the situation, when the rows need to be changed.
First of all, I don't use static cell identifier. I ask the data object to generate it (pay attention to [myObject signature]
, it provides a unique string describing all needed properties):
-(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]
provides different signatures for different list of properties. So, if my object is changed, I just call [self.tableView reloadData]
, myObject will provide a new signature, and the table load a new cell for it.
[myObject configureCell:cell]
places all needed subviews to the cell.
[myObject updateCellWithData:cell]
updates the subviews of the cell with current data.
回答3:
while ([tableView dequeueReusableCellWithIdentifier:@"reuseid"]) {}
回答4:
I have found a really good method.
In .h
//add this
int reloadCells;
In .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;
}
回答5:
I had to something similar today. I have a gallery of images, and when the user deletes them all, the gallery needs to get rid of the last queued cell and replace it with a "gallery empty" image. When the delete routine completes, it sets a 'purge' flag to YES, then does a [tableView reloadData]. The code in cellForRowAtIndexPath looks like this:
- (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
}
回答6:
The old data from previous usage of the cell should be cleared with the message prepareForReuse
. When a cell is dequeued this message is sent to the cell before it is returned from dequeueReusableCellWithIdentifier:
.
回答7:
No way, I think...
I used a completely different approach. Instead of relying on UITableView cache, I build my own. In this way I have perfect control on when and what to purge.
Something like this...
given:
NSMutableDictionary *_reusableCells;
_reusableCells = [NSMutableDictionary dictionary];
I can do:
- (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;
}
and:
-(void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
[_reusableCells removeAllObjects];
}
Hope this may help.
回答8:
I am coding ObjC (manual reference counting) and found some strange behavior that prevents UITableView and UITableViewCells from getting released, although my memory management is correct. UITableView and UITableViewCell seem to retain each other. And yes, I found a way to force the UITableView and its cells to release each other (remove cached cells).
To do this, basically you ask a table view for a reusable cell. The table view will remove it from its reusable cells cache. After all you tell that cell to remove from its superview. Done.
Now as a stand-alone class... First, you need an array of all cell identifiers you used. Next, you implement a dummy data source and let that data source clear the UITableView by reloading itself with the "ClearAllCachedCells" Data source:
#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
And the magic happens in the .m file:
#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