62 votes

iPhone - utilisation dequeueReusableCellWithIdentifier

Je travaille sur une application iPhone qui comporte un UITableView assez important avec des données provenant du web. J'essaie donc d'optimiser sa création et son utilisation.

J'ai découvert que dequeueReusableCellWithIdentifier est assez utile, mais après avoir vu de nombreux codes sources l'utiliser, je me demande si l'usage que je fais de cette fonction est le bon.

Voici ce que les gens font habituellement :

UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];

if (cell == nil) {
  cell = [[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"Cell"];

// Add elements to the cell
return cell;

Et voici comment je l'ai fait :

// The cell row
NSString identifier = [NSString stringWithFormat:@"Cell %d", indexPath.row]; 

UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:identifier];

if (cell != nil)
  return cell;

cell = [[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:identifier];
// Add elements to the cell
return cell;

La différence est que l'on utilise le même identifiant pour chaque cellule, de sorte que la mise en file d'attente d'une cellule évite d'en attribuer une nouvelle.

Pour moi, l'intérêt de la mise en file d'attente était de donner à chaque cellule un identifiant unique, de sorte que lorsque l'application demande une cellule qu'elle a déjà affichée, il n'y a ni allocation ni ajout d'élément à faire.

In fine Je ne sais pas ce qui est le mieux, la méthode "commune" limite l'utilisation de la mémoire du tableau au nombre exact de cellules qu'il affiche, tandis que la méthode que j'utilise semble favoriser la rapidité car elle conserve toutes les cellules calculées, mais peut entraîner une grande consommation de mémoire (à moins qu'il n'y ait une limite interne à la file d'attente).

Ai-je tort de l'utiliser de cette façon ? Ou est-ce que c'est au développeur d'en décider, en fonction de ses besoins ?

71voto

progrmr Points 32412

Le but de dequeueReusableCellWithIdentifier est d'utiliser moins de mémoire. Si l'écran peut contenir 4 ou 5 cellules de tableau, alors avec la réutilisation, vous n'avez besoin d'avoir que 4 ou 5 cellules de tableau allouées en mémoire, même si le tableau a 1000 entrées.

Dans la deuxième méthode, il n'y a pas de réutilisation. La deuxième méthode ne présente aucun avantage par rapport à l'utilisation d'un tableau de cellules de tableau. Si votre tableau comporte 1 000 entrées, vous aurez 1 000 cellules allouées en mémoire. Si vous faites cela, vous devez les mettre dans un tableau et indexer le tableau avec le numéro de ligne et retourner la cellule. Pour les petits tableaux avec des cellules fixes, cela peut être une solution raisonnable, mais pour les tableaux dynamiques ou volumineux, ce n'est pas une bonne idée.

0 votes

Vous avez raison de dire qu'avec ma méthode, un tableau pourrait faire l'affaire. Est-ce que ~100 cellules représenteraient une "trop" grande quantité de mémoire allouée en une seule fois ?

0 votes

Eh bien, j'ai changé pour la méthode commune (qui est compliquée quand une ligne a beaucoup de sous-vues), et en plus d'une consommation de mémoire plus faible, le défilement semble globalement plus fluide, je ne suis pas sûr pourquoi. Merci pour le conseil en tout cas !

0 votes

@progrmr ... merci... pour avoir clarifié le concept... c'est vraiment bien... je faisais aussi la même erreur... :)

19voto

Tim Gostony Points 7276

En ce qui concerne l'identifiant de la cellule, au lieu d'utiliser simplement "cell" pour l'identifiant, et au lieu d'utiliser un identifiant unique comme le PO, pourriez-vous utiliser un "identifiant de type" ? Par exemple, si ma table a 3 types de cellules - une avec une sous-mise en page très compliquée, une avec juste un nom d'utilisateur et un nom d'utilisateur. Style1 et un autre avec Style2 je devrais identifier ces trois-là séparément et ensuite les reconstruire si dequeue arrive nil .

Par exemple :

-(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath{
    NSString* ident = @"";
    if(indexPath.section == 0) ident= @"complicated";
    if(indexPath.section == 1) ident= @"style1";
    if(indexPath.section == 2) ident = @"style2";

    UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:ident];

    if(cell == nil){

       if(ident == @"complicated"){
          cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:ident] autorelease]; 
         // do excessive subview building
       }
       if(ident == @"style1"){
          cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyle1 reuseIdentifier:ident] autorelease]; 
       }

       if(ident == @"style2"){
          cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyle2 reuseIdentifier:ident] autorelease]; 
       }

    }
    if(ident == @"complicated"){
       // change the text/etc (unique values) of our many subviews
    }
    if(ident == @"style1"){
      [[cell textLabel] setText:@"Whatever"];
    }
    if(ident == @"style2"){
      [[cell textLabel] setText:@"Whateverelse"];
    }

    return cell; 
}

(Ce code ne fonctionnera probablement pas parce que je l'ai écrit ici, mais j'espère que vous comprenez l'idée. )

Je ne pense pas qu'Apple aurait créé l'idée de cellule réutilisable avec des identifiants s'ils voulaient que tous les identifiants soient "cell" Vous ne pensez pas ?

0 votes

Dans votre cas, j'utiliserais un identifiant pour chaque disposition de cellule. Ce qui signifie un pour vos cellules "compliquées", un pour les cellules "style1" et peut-être un troisième pour les cellules style2, si leurs subviews diffèrent de ceux de style1. Dans le cas où dequeue renvoie nil, ajoutez la sous-vue de la cellule avec des balises (définies dans un enum ou autre), puis initialisez-les. Dans le cas où dequeue renvoie une cellule, récupérez simplement les sous-vues à l'aide de balises et modifiez-les. Séparer votre code en une méthode pour chaque section serait bien aussi :)

13voto

je44ery Points 395

La documentation qui m'a aidé à comprendre pourquoi la méthode idiomatique (celle que vous avez décrite en premier) fonctionne le mieux est la suivante Référence de la classe UITableViewCell section sur le initWithStyle:reuseIdentifier: méthode.

El reuseIdentifier La sous-section se lit comme suit :

Vous devez utiliser le même identifiant de réutilisation pour toutes les cellules d'un même formulaire.

Et la sous-section "Discussion" se lit comme suit :

L'identifiant de réutilisation est associé aux cellules (lignes) d'une vue en tableau qui ont la même configuration générale, moins le contenu de la cellule.

Ces déclarations me font comprendre que la manière idiomatique d'utiliser dequeueReusableCellWithIdentifier à l'intérieur de votre implémentation de tableView:cellForRowAtIndexPath: pour votre UITableViewDataSource crée un objet cellule pour chaque visible rangée, quel que soit le nombre total de rangées disponibles.

4voto

AlexVogel Points 4574

Je pense que la première est la meilleure (et, comme vous l'avez dit, la plus courante) façon d'implémenter une UITableView . Avec votre deuxième méthode, de la mémoire sera allouée pour chaque nouvelle cellule affichée et aucune mémoire ne sera réutilisée.

0 votes

Eh bien la mémoire sera réutilisée si l'utilisateur fait défiler la page vers le bas puis la remonte... Ou chaque fois que le tableView accède à une cellule qui a été affichée une fois puis cachée.

0 votes

Bien sûr, mais il y aura une empreinte mémoire pour chaque cellule qui a été affichée et pas seulement pour les 4-5 cellules qui sont actuellement affichées. J'ai eu un problème similaire avec les annotations pour la carte. Le passage à un identifiant constant a apporté une augmentation notable des performances.

0 votes

Alors je suppose que c'est à moi de décider... Serait-il préférable de mettre en cache toutes les données que j'utilise pour créer la cellule plutôt que la cellule elle-même ?

1voto

Deepak G M Points 430

UITableView utilise en interne une cellule avec un identifiant comme "modèle". Ainsi, la prochaine fois que vous (lire comme table) essayez de déqueter, il crée simplement une nouvelle cellule mais en utilisant l'objet stocké comme modèle. Par conséquent, vous devez toujours mettre à jour son interface utilisateur pour refléter le contenu de la cellule en fonction du contexte.

Cela signifie également que le UITableView fait la gestion de la mémoire des cellules pour nous, en soi. En théorie, il n'y aura qu'un nombre limité UITableViewCell autant d'objets que de cellules visibles. Mais en pratique, il pourrait y en avoir quelques autres qui attendent d'être libérés de la mémoire.

Cela permet d'économiser beaucoup de mémoire, en particulier dans les scénarios où vous avez 1000 cellules.

Sur tout appareil portable où la mémoire est précieuse, nous devrions reporter l'allocation de toute mémoire au dernier moment possible et la libérer dès que son travail est terminé. dequeAndReusing une cellule y parvient et le fait plutôt bien.

D'autre part, si votre cellule est une cellule personnalisée, il est fort probable que nous puissions charger une plume et l'extraire. Si c'est le cas, vous pouvez soit utiliser un identificateur pour déqueter, soit le charger à partir de la plume. Il n'y a aucune différence dans la procédure.

La seule différence pourrait être le temps de chargement. Permettre à la vue Tableau de créer une nouvelle cellule en utilisant la cellule d'identification comme modèle pourrait est légèrement plus rapide que le chargement à partir de nib, mais c'est à peine perceptible et cela dépend du contexte.

0 votes

Les cellules personnalisées sont également gérées par le système de file d'attente de la mémoire du tableView, à condition qu'elles aient un numéro d'identification Identifier set.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X