Quand utiliser le compteur de références et l'autorelease pool ?
chkdsks
Membre
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
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
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
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
- 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
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...
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 : 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"
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.
• 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.....
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
ps: on ne citera pas de nom de langage.. :P
[Edit] Il faut taper à l'invite de gdb :
[tt]print (int)[maVariable retainCount][/tt]
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)
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é.
[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
ps: il faut aussi penser à faire un #import
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 ?