Quand utiliser le compteur de références et l'autorelease pool ?

chkdskschkdsks Membre
février 2011 modifié dans API AppKit #1
Bonjour,

Je suis en train de gérer les fuites mémoires et optimiser la gestion de la mémoire... et je me suis demandé dans quels cas on doit utiliser les mécanismes :
- compteur de références > alloc et méthodes en - : init puis release et nil souvent
- autorelease > méthodes en + : puis possible drain dans un thread explicite

• J'aurai tendance à  utiliser :
- l'autorelease pour les "petits" objets, à  faible capacité mémoire, et dont le scope, la visibilité de la variable, est court
- le compteur de références pour les autres cas

Sauf que je pense que la gestion manuelle est préférable dans un thread "personnel" puisque la méthode drain de la classe NSAutoreleasePool doit consommer pas mal en temps d'exécution (c'est relatif).

• Sinon dans quels cas il est préférable de ne pas mettre une variable à  nil après un release ? quand on utilise @synthesize ?

Quand utiliser vraiment retain ?
Comment gérez vous en mémoire les objets en valeur de retour d'une méthode ?

En fait quand les utilisez vous ? :)
Merci

Réponses

  • AliGatorAliGator Membre, Modérateur
    13:11 modifié #2
    En règle générale, le mieux / l'idéal c'est de toujours utiliser "release" quand on peut, et n'utiliser "autorelease" que quand on ne peut pas utiliser "release" directement (typiquement quand une méthode crée un objet puis le retourne, c'est à  la méthode d'équilibrer son alloc/init par un release mais elle ne peut pas appeler "release" avant de retourner l'objet à  la méthode appelante... et après le return il sera trop tard... donc c'est LE cas où on n'a pas le choix et doit utiliser autorelease)

    Après ça reste tentant d'utiliser autorelease ou les constructeurs de commodité dans beaucoup de cas car c'est plus rapide à  écrire bien souvent, mais comme tu le dis ça va pour les petits objets.
    Si tu manipules des gros objets, comme des UIImage par exemple, il est conseillé :
    - Soit de les releaser dès que tu ne les utilises plus (alors que "autorelease" ne va les release que lorsque l'autoreleasepool sera vidée)
    - Soit prévoir une NSAutoreleasePool interne, par exemple si tu manipules des UIImage dans une boucle for qui ne servent que dans la boucle, tu peux commencer la boucle for par un [tt]NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];[/tt] et la terminer juste avant son accolade fermante par [tt][pool release];[/tt] comme ça tous les objets créés entre ces deux lignes dans la boucle et étant "autoreleased" seront mis dans l'autoreleasepool "pool" locale (et non dans l'ARP "globale à  ton main thread") et donc releasés au moment du [tt][pool release][/tt] sans attendre la fin de la RunLoop.



    Sinon pour les variables à  nil après release, il est toujours préférable de le faire. Les seuls cas où ça passe de ne pas le faire c'est :
    - si juste après ce "release", on réaffecte la variable à  autre chose. Par exemple si on écrit
    [toto release];<br />// toto = nil; // on va pas la mettre à  nil ici alors qu&#39;on va la réaffecter juste après ça sert à  rien<br />toto = [newToto retain];
    

    - quand on fait les "release" dans le "dealloc", puisque typiquement le dealloc est appelé à  la destruction de l'objet, donc après ce "dealloc" on n'est jamais sensé avoir accès aux variables d'instance manipulées par l'objet, donc il n'y a pas de raison que les mettre à  "nil" pour éviter d'y accéder une fois releasées, puisqu'après ce dealloc on ne pourra pas y accéder de toute façon.
    Ceci dit j'ai appris très récemment que ce point n'était pas tout à  fait vrai puisque dans le cas particulier de UIViewController la doc mentionne quà  cause d'un détail d'implémentation du dealloc de UIViewController, il faut remettre ses IBOutlets à  nil dans le dealloc (bien que je pense que c'est pour que ça soit compatible avec les versions de iOS avant l'arrivée de "viewDidUnload" et que maintenant ce n'est plus vrai, mais on n'est jamais trop prudent)



    Où il est dit dans la doc de remettre les outlets d'un UIViewController à  nil
    où il est expliqué dans la doc les conseils de gestion mémoire en particulier qu'il est préférable d'appeler release plutôt qu'autorelease ou de charger les resources "lazily", réduire le footprint de son appli, et penser à  gérer les memoryWarnings
  • chkdskschkdsks Membre
    février 2011 modifié #3
    Merci AliGator, je suis curieux :P

    Techniquement il y a quoi dans la définition d'un [NSString stringWithFormat:@..., ...] par exemple ? est ce qu'il y a du code de commun entre la méthode d'instance et la méthode de classe ? en fait je me suis toujours demandé comment on pouvait en écrire soit même...
  • zoczoc Membre
    février 2011 modifié #4
    L'implémentation de stringWithFormat est triviale (edit: non en fait pas si triviale à  cause du nombre d'arguments variables  ??? , mais sinon généralement un constructeur de commodité se résume à  ce qui suit) :


    <br />+ (NSString *)stringWithFormat:....<br />{<br />&nbsp; &nbsp; return [ [ [self alloc] initWithFormat:@&quot;...&quot;, ...] autorelease];<br />}
    
  • AliGatorAliGator Membre, Modérateur
    13:11 modifié #5
    Oui et pour écrire des constructeurs de commodité pour tes classes c'est pareil.
    @implementation MaClasse<br />-(id)initWithToto:(NSString*)toto {<br />&nbsp; // ...<br />}<br />+(id)maClasseWithToto:(NSString*)toto {<br />&nbsp; return [[[self alloc] initWithToto:toto] autorelease];<br />}
    


    Petite subtilité, pour les méthodes de classe (comme maClasseWithToto), préférer utiliser "self" (qui ici représente la classe elle-même et non pas un objet/l'instance courante) plutôt que d'utiliser le nom de la classe pour le message "alloc". En effet cela permet, dans le cas où tu fais une sous-classe de MaClasse en MaSousClasse (mais que tu ne surcharges pas maClasseWithToto dans cette sous-classe) de bien créer une instance de la bonne classe :
    @interface MaSousClasse : MaClasse<br />...<br />@end<br /><br />// ailleurs dans le code<br />id obj = [MaSousClasse maClasseWithToto:@&quot;plouf&quot;];
    
    Si tu mets pas [tt][self alloc][/tt] dans le constructeur de commodité (méthode de classe) de MaClasse mais que tu mets [tt][MaClasse alloc][/tt] à  la place, alors tu auras beau avoir appelé [tt][MaSousClasse maClasseWithToto:@plouf][/tt], c'est dans tous les cas [tt][MaClass alloc][/tt] qui sera appelé donc une instance de "MaClasse" qui sera créée contrairement à  ce à  quoi tu peux t'attendre. Alors que si dans la méthode de classe tu mets [tt][self alloc][/tt] c'est une instance de la classe sur laquelle tu as appelé la méthode qui sera créée, donc dans mon exemple ça sera bien une instance de MaSousClasse


    --
    PS : petite coquille de la part de zoc, c'est bien un "+" qu'il faut devant la définition de la méthode "stringWithFormat" ;)
  • zoczoc Membre
    février 2011 modifié #6
    dans 1297960463:

    PS : petite coquille de la part de zoc, c'est bien un "+" qu'il faut devant la définition de la méthode "stringWithFormat" ;)

    Je corrige  :P

    Et pour le [self alloc], ça se voit que je ne pratique pas assez l'objective-c  ;) , parce que c'est évidemment ce qu'il faut faire.


    En ce qui concerne les listes d'arguments variables, il existe probablement une méthode initWithFormat privée prenant en paramètre un va_list.


  • chkdskschkdsks Membre
    février 2011 modifié #7
    Ah oui effectivement ;), je ne l'ai jamais codé pour mes propres objets mais ça m'arrive de l'écrire lorsque je n'ai pas la méthode de classe équivalente à  la méthode d'instance dans le Mac SDK, d'ailleurs je ne suis pas sûr que cela soit une bonne idée vu qu'Apple ne l'a pas écrite... par exemples :

    • NSMutableParagraphStyle
    Il y a +defaultParagraphStyle mais je ne crois pas que cela fasse la même chose qu'un +alloc puis -init avec -autorelease

    • NSImage
    Pas d'équivalent pour -initWithSize: pour dessiner dedans, utile pour moi pour les petites images

    • NSMutableAttributedString
    Rien, c'est peut être un "gros" objet...

    Pas sûr que cela soit de bonnes pratiques.....
  • AliGatorAliGator Membre, Modérateur
    13:11 modifié #8
    dans 1297960836:

    Et pour le [self alloc], ça se voit que je ne pratique pas assez l'objective-c  ;) , parce que c'est évidemment ce qu'il faut faire.

    En ce qui concerne les listes d'arguments variables, il existe probablement une méthode initWithFormat privée prenant en paramètre un va_list.
    En effet ça sent le manque de pratique, reviens du bon côté de la force zoc ! :D

    Parce que là  non plus il n'y a pas besoin d'une méthode privée, il y a une méthode publique qui permet ça sans souci, il s'agit de initWithFormat:arguments: de NSString tout bêtement, où tu passes un va_list au paramètre arguments, ce qui te permet donc de gérer les méthodes aux nombres variables d'arguments ;)
  • chkdskschkdsks Membre
    février 2011 modifié #9
    Comment peut on connaà®tre l'état du compteur de références dans gdb ? j'ai fait un breakpoint sur une NSImage (elle n'est pas en autorelease), je ne vois rien dans la partie sur NSObject... :'(

    ps: on ne citera pas de nom de langage.. :P

    [Edit] Il faut taper à  l'invite de gdb :
    [tt]print (int)[maVariable retainCount][/tt]
  • AliGatorAliGator Membre, Modérateur
    13:11 modifié #10
    Oui et "p" au lieu de "print" marche aussi (c'est un raccourci, comme "po" est un raccourci de la commande gdb "print-object") pour info

    Par contre attention à  cette valeur retournée par retainCount : se baser sur le retainCount est "dangereux", je m'explique : ça peut aider éventuellement à  comprendre un peu les mécanismes, et encore, mais le problème c'est que tu ne sais pas ce qui se passe sous le capot et à  l'intérieur par exemple des frameworks Apple.

    Ainsi, si par exemple tu crées une UIImage et l'associe à  la propriété "image" d'une UIImageView, tu pourrais croire que le retainCount est de 1 car tu viens de créer l'image et n'a pas fait de retain... mais la UIImageView en a très certainement fait un, elle, de retain sur l'image. Et peut-être même que la boucle de composition fait aussi un retain sur l'image elle aussi le temps de la dessiner, que sais-je. Ce n'est pas un problème, car tous ces objets qui font des retain ce sont eux qui sont aussi responsables de faire des release, donc la balance sera retrouvée... mais en attendant, si tu as un retainCount de 3 alors que tu t'attendais à  une valeur de 1 ou 2... c'est pas forcément une erreur de ta part, c'est peut-être juste parce qu'un objet interne a fait lui aussi un retain sur ton objet alors que ce n'est pas forcément évident/explicitement fait, mais comme tu ne sais pas trop ce qui se passe sous le capot dans les frameoworks Apple...

    De même, quand on envoie un message "autorelease" à  un objet, cet objet est seulement mis dans la NSAutoreleasePool : son retainCount ne descend évidemment pas tout de suite. Du coup si tu as du code du genre [tt][[obj retain] autorelease][/tt] (appelé directement ou indirectement), le retainCount augmente de 1, pourtant ça veut pas dire qu'il y a une fuite mémoire, puisque le retainCount va rebaisser de 1 plus tard, c'est prévu grâce à  l'autoreleasepool... mais ça tu ne le fois pas quand tu demandes juste le retainCount !

    Enfin 3e et dernier piège avec le retainCount, si la classe de l'objet que tu manipules utilises des mécanismes comme le COW (Copy-On-Write), ou le pattern Singleton, tu pourras avoir ce que tu crois être plusieurs instances d'un même objet dans ton code alors que ce sont en réalité un seul et même objet avec un retainCount qui grossit, dans le cas du singleton il a même une valeur spéciale (UINT_MAX), dans le cas d'une constante ça doit être pareil aussi...

    Bref des cas spéciaux qui sont implémentés de sorte que pour toi ça soit toujours aussi transparent, tu continues d'utiliser des retain (ou alloc/init, ou copy) et des release (ou autorelease), et à  respecter les règles de gestion mémoire comme avant sans te soucier de ces spécificités qui sont masquées par une implémentation intelligente les rendant transparentes... mais n'empêche que le retainCount, que tu n'es pas spécialement sensé consulté, va te retourner des valeurs qui pourraient t'étonner et ne pas être celles que tu attendes à  cause de ces spécificités.


    Voilà  pourquoi, pour résumer, de manière générale, consulter le retainCount risque parfois plus de t'embrouiller que de t'aider à  mieux comprendre les choses ou à  trouver une fuite mémoire. En bref il ne faut pas trop s'y fier, enfin en tout cas il ne faut pas tirer de conclusion rien qu'en consultant le retainCount (tu pourrais croire qu'il est mauvais à  cause d'une fuite mémoire alors que c'est normal ou des trucs comme ça alors que ça ne serait pas le cas)
  • chkdskschkdsks Membre
    février 2011 modifié #11
    D'accord, en fait c'est pour savoir si mon objet est vraiment désalloué quand je fais un release (le retainCount doit passer à  0), quelle est la méthode la plus simple pour le savoir ? faut-il forcément tracer la pile d'appels pour voir s'il y a un -dealloc ?
  • AliGatorAliGator Membre, Modérateur
    13:11 modifié #12
    Tu ne pourras jamais voir si ton retainCount passe à  zéro, puisque par définition au moment où il passe à  zéro l'objet est détruit et donc à  ce moment là  il est trop tard pour demander le retainCount et qu'il te retourne zéro ;)

    Par contre nul besoin de tracer la pile d'appels ou quoi. il suffit de mettre un NSLog ou un breakpoint dans le -dealloc, méthode qui est appellée automatiquement quand ton objet est désalloué.
  • chkdskschkdsks Membre
    février 2011 modifié #13
    Ah zut.. pour mes propres objets mais pour les objets du SDK il faut que je fasse une category (sur NSObject pour simplifier le travail) :o
    [tt]
    @implementation NSObject (NSObjectDealloc)

    - (void)dealloc
    {
        if([[self className] isEqualToString:@NSImage])
              NSLog(@NSImage désallouée...); // breakpoint
       
        // on peut mettre ce que l'on veut
    }

    #end
    [/tt]
    Par contre j'ai un warning comme quoi je n'ai pas de [super dealloc]... je crois qu'il n'aime pas trop :D

    ps: il faut aussi penser à  faire un #import
  • AliGatorAliGator Membre, Modérateur
    13:11 modifié #14
    Oulà à à à  !

    TREEEESS Déconseillé de surcharger des méthodes existantes dans des catégories
    Car puisque c'est une catégorie, tu ne peux pas appeler la méthode parente ! Ce n'est pas une sous-classe
    Or ne pas appeler le dealloc de NSObject, ça me parait en plus très moyen (meilleur moyen de faire une fuite mémoire que de ne pas appeler le dealloc de la classe parente)

    A la limite quand on veut réimplémenter une méthode déjà  implémentée pour remplacer son implémentation donc, il faut utiliser le fait que l'Objective-C soit dynamique et donc utiliser le Method Swizzling (donc les méthodes du Runtime ObjC) pour inverser les implémentations. Mais c'est déjà  un peu plus haut comme niveau (quelques petites subtilités) et puis je comprend toujours pas l'intérêt de faire ça.

    Pourquoi ne pas mettre simplement un breakpoint, soit dans le dealloc de ta classe si tu veux savoir uniquement les dealloc faits sur ton objet, soit un breakpoint non pas sur une ligne de code, mais sur le symbole "-[NSObject dealloc]" directement ? Pour qu'ainsi il se déclenche sur tous les appels à  -dealloc ?
  • chkdskschkdsks Membre
    13:11 modifié #15
    Parfait le breakpoint -[NSImage dealloc] ! Merci
Connectez-vous ou Inscrivez-vous pour répondre.