2010-02-18 4 views
6

Est-ce que quelqu'un a des suggestions sur la façon de purger un cache UITableViewCell?Comment purger un UITableViewCell en cache

Je souhaite mettre en cache ces cellules avec reuseIdentifier. Cependant, il y a des moments où j'ai besoin de supprimer ou de modifier certaines lignes de la table. Je m'attends à appeler reloadData après les changements de ligne.

À l'heure actuelle, dequeueReusableCellWithIdentifier renvoie toujours l'entrée mise en cache (obsolète) d'avant. Comment puis-je indiquer que le cache est périmé et doit être purgé?

Répondre

0

Les anciennes données de l'utilisation précédente de la cellule doivent être effacées avec le message prepareForReuse. Lorsqu'une cellule est supprimée, ce message est envoyé à la cellule avant d'être renvoyé par dequeueReusableCellWithIdentifier:.

+0

OK merci. J'espérais trouver un moyen d'invalider toute la cache d'un seul coup. Cependant, cette solution (effacez manuellement les données pour invalider le cache et faire l'invalidation, une ligne à la fois) devrait aussi fonctionner –

+2

Je ne pense pas que vous compreniez la réponse de Giao. Il n'a rien suggéré du tout pour toi. La méthode 'prepareForReuse:' est envoyée à chaque cellule lorsqu'elle est supprimée, avant qu'elle ne vous soit remise. C'est quelque chose qui se passe automatiquement, et vous vous en souciez seulement si vous implémentez votre propre sous-classe de UITableViewCell. –

7

Je ne sais pas pourquoi vous essayez de purger les cellules en premier lieu. Chaque fois que vous supprimez une cellule, vous devez redéfinir toutes les données qui doivent être affichées. La mise en cache vous empêche simplement de configurer des propriétés non modifiables à chaque fois. Mais les données réelles affichées doivent être définies, même si la cellule a déjà été mise en cache. Notez que votre identifiant de réutilisation est supposé être le même pour toutes les cellules du même type. Si vous faites quelque chose de stupide comme le calcul de l'identifiant basé sur la ligne en question, alors vous le faites mal.

Votre code devrait ressembler à

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

Dans cet exemple, notez comment je fixe toujours les données pour la cellule à chaque fois, et toutes les cellules partagent le même identifiant. Si vous suivez ce modèle, vous ne devriez avoir aucune raison d'essayer de "purger" vos cellules, car les anciennes données seront écrasées de toute façon. Si vous avez plusieurs types de cellules, vous pouvez utiliser plusieurs identifiants dans ce cas, mais il s'agit toujours d'un identificateur par type de cellule.

+0

L'utilisation d'un identifiant différent pour chaque ligne est un moyen facile de charger des miniatures distantes de manière asynchrone sur chaque cellule de table. Sinon, la cellule pourrait charger les vignettes qui étaient affichées dans les autres lignes chargées auparavant. Si vous avez une recommandation pour gérer cette situation avec élégance, j'aimerais savoir. –

+2

@Jesse Armand: Utiliser un identifiant différent pour chaque ligne est un excellent moyen de fuir chaque cellule. Bien sûr, ce ne sont pas des "vraies" fuites, car elles disparaîtront une fois que la table le fera elle-même, mais vous mettrez en cache agressivement des objets qui ne seront plus jamais utilisés. Vous avez juste besoin d'être plus intelligent à propos de l'annulation de la charge des vignettes lorsque la cellule est réutilisée, ou de vérifier que la cellule représente toujours le même objet lorsque votre vignette est prête à être affichée. –

+2

@Yar: Si vous ne partagez pas votre identificateur entre des cellules, vous «fuyez» intentionnellement chaque cellule que vous créez (je l'ai mis entre guillemets car il ne fuit que tant que la table est active). Et si vous n'utilisez pas 'dequeue', vous êtes, encore une fois," fuyant "chaque cellule que vous créez. Vous n'économisez pas * un * alloc/init, vous économisez * CHAQUE INDICE * alloc/init après le premier 7 (ou plus, en fonction de la hauteur de la cellule et de la hauteur de la table, simplement 'numCellsVisible + 2'). –

2

J'ai trouvé une très bonne méthode.

Dans .h

//add this 
int reloadCells; 

Dans .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; 
} 
+1

Pourquoi la downvote? –

+0

Je ne pense pas que la personne a aimé la constante "12." Vous devez choisir une valeur plus grande que le nombre maximum de cellules qui devront être mises en cache - peut-être une supposition. Mais, la solution est si simple, et cela fonctionne !, donc je lui donne une upvote. – Jeff

+0

Comment '(void) dumpCache' est-il appelé dans ce scénario? – topLayoutGuide

1

j'avais quelque chose aujourd'hui similaire. J'ai une galerie d'images, et quand l'utilisateur les supprime toutes, la galerie doit se débarrasser de la dernière cellule mise en file d'attente et la remplacer par une image "galerie vide". Lorsque la routine de suppression est terminée, elle définit un indicateur 'purge' sur YES, puis une [tableView reloadData]. Le code cellForRowAtIndexPath ressemble à ceci:

- (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 
} 
+0

Cela a fonctionné pour moi - la seule différence était que je devais passer à travers toutes les cellules puis régler purge = NO. J'ai ajouté: if (([ligne de chemin d'index] +1) == (listOfItems.count)) { self.purge = 0; } juste avant la cellule de retour. – Jennifer

0

No way, je pense ... je une approche complètement différente. Au lieu de me fier au cache UITableView, je construis le mien. De cette façon, j'ai un contrôle parfait sur quand et quoi purger. Quelque chose comme ça ...

donné:

NSMutableDictionary *_reusableCells; 
_reusableCells = [NSMutableDictionary dictionary]; 

que je peux faire:

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

et:

-(void)didReceiveMemoryWarning 
{ 
    [super didReceiveMemoryWarning]; 
    // Dispose of any resources that can be recreated. 
    [_reusableCells removeAllObjects]; 
} 

Hope this peut aider.

3

Je ne sais pas comment purger le cache, cependant, j'utilise une solution de contournement pour gérer la situation, lorsque les lignes doivent être modifiées. Tout d'abord, je n'utilise pas d'identificateur de cellule statique. Je demande à l'objet de données pour générer (attention à [myObject signature], il fournit une chaîne unique décrivant toutes les propriétés nécessaires):

-(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] fournit des signatures différentes pour la liste des différentes propriétés. Donc, si mon objet est changé, j'appelle simplement [self.tableView reloadData], myObject fournira une nouvelle signature, et la table chargera une nouvelle cellule pour cela.

[myObject configureCell:cell] place toutes les sous-vues nécessaires à la cellule.

[myObject updateCellWithData:cell] met à jour les sous-vues de la cellule avec les données actuelles.

+1

C'est une bonne idée. Malheureusement, cela ne fonctionne pas avec les cellules chargées depuis un XIB, puisque l'identificateur utilisé dans cellForRowAtIndexPath doit correspondre à l'identifiant défini dans la XIB. – MusiGenesis

3
while ([tableView dequeueReusableCellWithIdentifier:@"reuseid"]) {} 
+1

le soin d'élaborer? –

+0

C'est une bonne solution. Lorsque vous supprimez la file d'attente, vous transmettez un identifiant de réutilisation. Si vous modifiez/incrémentez l'identifiant de réutilisation, cela générera une nouvelle cellule. Vraisemblablement, iOS ramasse les vieilles cellules quand/si elle en a besoin pour gérer la mémoire. –

0

Je suis codage ObjC (de comptage de référence manuel) et a trouvé un comportement étrange qui empêche UITableView et UITableViewCells de se libéré, bien que ma gestion de la mémoire est correcte. UITableView et UITableViewCell semblent se conserver mutuellement. Et oui, j'ai trouvé un moyen de forcer l'UITableView et ses cellules à se libérer l'une l'autre (supprimer les cellules mises en cache). Pour ce faire, vous demandez essentiellement une vue de table pour une cellule réutilisable. La vue de table le supprimera de son cache de cellules réutilisable. Après tout, vous dites à cette cellule de supprimer de son aperçu. Terminé.

Maintenant, en tant que classe autonome ... Tout d'abord, vous avez besoin d'un tableau de tous les identifiants de cellules que vous avez utilisés. Ensuite, vous implémentez une source de données factices et laissez cette source de données effacer le UITableView par lui-même rechargeant avec les « ClearAllCachedCells » Source de données:

#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 

Et la magie se produit dans le fichier .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 
Questions connexes