Questions precises sur la gestion mémoire
Djobird
Membre
Bonjour,
j'ai lu pas mal de tuto sur la gestion mémoire, je commence a comprendre, néanmoins il y a deux choses que je n'arrive pas à trouver.
Concernant les @property.
Si j'ai par exemple ceci dans le .h d'une classe C1 :
@property(retain) NSString* val1;
En effectuant :
NSString* val2 = c1.val1;
Cela va bien effectuer un retain sur val1, et avoir son retaincount à 2 ? Ce qui va m'obliger à faire un release sur val2 dés que je n'en ai plus besoin pour remettre son retaincount à sa valeur initiale ?
Maintenant, si je fais quelque chose de ce genre :
NSString* val2 = [c1.val1 stringByAppendingString @coucou];
Pour val2, j'ai un autorelease dessus grace à la méthode stringByAppendingString. Pour @coucou j'ai pas bien compris comment ca marchait, mais j'ai compris que j'avais pas à faire de relase.
En revanche, pour c1.val1, quand je fais ca, j'ai bien un retain sur val1 ? Est ce que j'ai alors une memoryleak ? Si oui, le seul moyen de l'en empêcher c'est de mettre c1.val1 dans une variable temporaire sur la quelle j'effectuerais un release ou il y a plus simple ?
j'ai lu pas mal de tuto sur la gestion mémoire, je commence a comprendre, néanmoins il y a deux choses que je n'arrive pas à trouver.
Concernant les @property.
Si j'ai par exemple ceci dans le .h d'une classe C1 :
@property(retain) NSString* val1;
En effectuant :
NSString* val2 = c1.val1;
Cela va bien effectuer un retain sur val1, et avoir son retaincount à 2 ? Ce qui va m'obliger à faire un release sur val2 dés que je n'en ai plus besoin pour remettre son retaincount à sa valeur initiale ?
Maintenant, si je fais quelque chose de ce genre :
NSString* val2 = [c1.val1 stringByAppendingString @coucou];
Pour val2, j'ai un autorelease dessus grace à la méthode stringByAppendingString. Pour @coucou j'ai pas bien compris comment ca marchait, mais j'ai compris que j'avais pas à faire de relase.
En revanche, pour c1.val1, quand je fais ca, j'ai bien un retain sur val1 ? Est ce que j'ai alors une memoryleak ? Si oui, le seul moyen de l'en empêcher c'est de mettre c1.val1 dans une variable temporaire sur la quelle j'effectuerais un release ou il y a plus simple ?
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
Il s'agit juste d'une simple affectation dans ce cas.
En fait il faut voir les @property juste comme une façon simplifier de coder et appeler les setters/getters d'une variable.
Si tu as [tt]@property(retain) NSString* val1;[/tt] dans ta classe C1, alors :
- appeler [tt]x = c1.val1;[/tt] revient exactement à appeler [tt]x = [c1 val1];[/tt] (autrement dit appeler le "getter", la méthode qui va te retourner la valeur de val1, point barre)
- appeler [tt]c1.val1 = x;[/tt] revient exactement à appeler [tt][c1 setVal1:x];[/tt] (autrement dit appeler le "setter", la méthode qui va affecter une nouvelle valeur à val1).
Le "getter" se contente toujours de retourner juste la valeur de la variable, il ne fait rien d'autre :
Par contre le "setter" va affecter une nouvelle valeur à la variable, et va suivre la politique de gestion mémoire indiquée. Ainsi, si tu avais déclaré ta propriété via [tt]@property(assign)[/tt], le setter implémenté par [tt]@synthesize[/tt] correspondra à : Mais comme tu as déclaré ta propriété via [tt]@property(retain)[/tt], le setter, implémenté automatiquement par [tt]@synthesize[/tt], correspondra à :
Donc [tt]c1.val1 = val2[/tt] fait un "retain" sur val2 au moment de l'affecter à val1, et le "release" correspondant se fera quand tu réaffecteras une nouvelle autre valeur à c1.val1 (ou quand c1 sera détruit, si tu as bien mis le [tt][val1 release][/tt] qui va bien dans le code de [tt]-(void)dealloc[/tt] de C1 comme il se doit pour les @property(retain)) ; mais [tt]val2 = c1.val1[/tt] ne fait aucunement de retain.
Pas du tout
l'attribut "retain" de la propriété indique qu'un retain sera fait sur l'objet lors de l'affectation d'une valeur à la propriété, pas de l'accès à la valeur de la propriété.
Oui.
Parce qu'en fait "@coucou" est une chaine constante. Elle n'existe qu'en un seul exemplaire dans un programme quelque soit le nombre de fois où elle aparait, et bien qu'il soit possible de faire des retain et release dessus, ces opérations ne font strictement rien.
Dans [c1.val1 string....] comme dans ton exemple ? non, pas du tout.
ya encore du boulot
Ah c'est donc pour ça que même si on fait un [[NSString alloc] initWithString:@coucou] et bien ca ne change rien qu'on le release ou pas ?
J'avais fais quelques test et j'ai trouvé ça assez étonnant. En plus l'analyzer de clang ne bronche pas quand on fait pas de release dessus.
a priori "initWithString" est implémentée de la manière suivante:
Dans le cas d'une chaine constante, retain ne fait rien, donc release ne fait rien non plus, et l'oublier ne produira pas de leak.
Par contre, une chose que je ne comprend pas à ce moment.
Si je fais :
NSMutableArray* tab2 = c1.tab1;
tab2 point bien vers la même valeur que tab1 ?
Donc si j'ajoute une valeur dans tab1, la valeur sera aussi ajouter dans tab2 ?
A partir de là , si je fais un release sur tab1, l'objet va alors être désaloué (en supposant qu'aucun retain n'a été fait ailleurs of course) et tab2 pointera sur nil ? Et idem si je fais le release sur tab2 ?
oui, tab2 pointe sur la même instance de NSMutableArray que c1.tab1;
Oui, puisque tab1 et tab2 son un seul et unique "tableau".
Oui, en cas de release l'objet va être désalloué. Mais non, la valeur de tab2 ne va pas miraculeusement passer à nil. tab2 pointera toujours vers la zone mémoire qui était utilisée avant la désallocation de l'objet, et du coup, son utilisation entrainera immanquablement un plantage de l'application.
je dois avoir rien compris en fin de compte.
Je vérifie les leaks de mon appli avec bah l'outil Leaks.
Et j'en ai quelque une en effet, qui je pense viennent globalement du même endroit.
J'ai par exemple ceci :
Ce qui me ramène à cette fonction :
Mais je ne comprend pas, ou c'est qu'il y a une fuite ici ?
Et à la limite (ça dépend comment fonctionne l'application), il faudrait faire un [tt]release[/tt] sur [tt]remotePicturesList[/tt] s'il n'est pas nul.
Edit : cela dit, ce n'est pas ça qui plante, mais le [tt]initWithContentsOfURL[/tt]. Cela signifie certainement que l'url est mal formée, et donc la variable [tt]remoteCatalogXmlFileURL[/tt] ne contient peut être pas une bonne valeur.
Pour remotePicturesList il s'agit d'une string obtenue par un define. Cela peut venir de là quand même ? Oo
Merci pour l'idée sur l'url mal formée, je regarde ca.
Bon je ne vois pas trop ce qui peut clocher au niveau de l'url.
voici le define de remoteCatalogXmlFileURL.
#define remoteCatalogXmlFileURL [[NSString stringWithString:@"http://www.siteDeDjobird.com/bird/patternCatalog.xml"]autorelease]
J'ai aussi essayé avec un simple
#define remoteCatalogXmlFileURL @"http://www.siteDeDjobird.com/bird/patternCatalog.xml", mais c'est même la chose.
Mais bon, cela ne change rien au problème. Là , je ne vois pas où pourrait être le problème avec le [tt]initWithContentsOfURL[/tt].
Cela dit, le leak vient peut être d'Apple. Par exemple, j'ai lancé un projet avec Instrument et analyse des Leaks, j'ai 2 leaks qui ne font aucunement référence à mon code (si ce n'est le main.m qui est celui par défaut).
Ce define est faux: stringWithString ne commence pas par "init" ou "copy", et par conséquent il retourne un objet sur lequel "autorelease" a déjà été appelé. Donc il ne faut pas d'autorelease supplémentaire.
Accessoirement "stringWithString" ne sert strictement à rien, et ta deuxième version du define est suffisante. Maintenant, utiliser des define pour ce genre de chose c'est pas génial. La bonne façon de faire, c'est de définir une "chaine constante". En ce qui me concerne, je regroupe toutes mes constantes dans un fichier unique :
L'avantage ? D'une part regrouper les constantes permet d'y avoir accès facilement et de ne pas devoir les chercher quand on veut un modifier la valeur, et d'autre part, en cas de modification d'une ou plusieurs constantes, seul le fichier Constants.m sera recompilé, ce qui, généralement, est un gros gain de temps par rapport à un define.
En ce qui concerne le leak, Cette méthode est-elle appelée plusieurs fois ? Parce que si la réponse est oui, alors il y a un potentiel leak sur remotePicturesList: Tu lui affectes un nouveau tableau sans faire un release sur le précédent... Pour éviter tout problème, il faudrait plutôt écrire:
Sinon, merci pour les constantes, je verrais a utiliser ca à présent (j'ai un peu trop de constantes actuellement pour faire la modif de suite).
Pour la leak, non, cette methode n'est appellée qu'une seule fois
J'ai essayé de décomposer un peu plus ma fonction au niveau de NSData :
Et cette fois la fuite vient bien NSData *data = [[NSData alloc] initWithContentsOfURL:url];
De plus ce que je ne comprend pas, c'est qu'elle va etre détectée aléatoirement. Cette fonction est appelée au tout début de l'appli et une unique fois. Pourtant, la memory leak n'est pas détectée de suite, parfois je vais devoir faire actions, d'autres load/unload un tas de vue différentes avant que la leak ne soit détectée ...
Sinon à propos de la fuite détecter "aléatoirement" je suppose que ça n'est détecté qu'à la fin d'un runloop, donc il peut ce passé des choses avant.
merci bien pour vos réponses.
Après recherche, il semble que beaucoup de monde ait le même problème de memory leak, et certain ont la même curiosité que moi, c'est à dire que la memory leak apparait uniquement lorsque j'affiche un UIWebView.
Bref je sais pas si c'est un bug du framwork ou pas (je doute certaines dicussion sur ce sujet date de 2002 ...).
Donc je pense que je vais envoyer mon appli comme ca en espérant que Apple laisse passer cette unique memory leak (quelques octets sur la dizaine de mega alloué ca va a priori ...)