Questions precises sur la gestion mémoire

DjobirdDjobird 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 ?

Réponses

  • AliGatorAliGator Membre, Modérateur
    13:54 modifié #2
    dans 1269176957:

    En effectuant :
    NSString* val2 = c1.val1;
    Cela va bien effectuer un retain sur val1, et avoir son retaincount à  2 ?
    Pas du tout.
    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 :
    -(NSString*)val1 { return val1; }
    

    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 à  :
    -(void)setVal1:(NSString*)newVal {<br />&nbsp; val1 = newVal; // et c&#39;est tout, juste une assignation<br />}
    
    Mais comme tu as déclaré ta propriété via [tt]@property(retain)[/tt], le setter, implémenté automatiquement par [tt]@synthesize[/tt], correspondra à  :
    -(void)setVal1:(NSString*)newVal {<br />&nbsp; if (val1 != newVal) {<br />&nbsp; &nbsp; [val1 release]; // on fait un &quot;release&quot; sur la valeur précédente avant de l&#39;écraser<br />&nbsp; &nbsp; val1 = [newVal retain]; // on écrase la valeur précédente par la nouvelle... après avoir fait un &quot;retain&quot; dessus<br />}
    


    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.
  • zoczoc Membre
    13:54 modifié #3
    dans 1269176957:

    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 ?

    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é.


    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.

    Oui.


    Pour @coucou j'ai pas bien compris comment ca marchait, mais j'ai compris que j'avais pas à  faire de relase.

    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.


    En revanche, pour c1.val1, quand je fais ca, j'ai bien un retain sur val1 ?

    Dans [c1.val1 string....] comme dans ton exemple ? non, pas du tout.


    ya encore du boulot  :D
  • JegnuXJegnuX Membre
    13:54 modifié #4
    dans 1269180524:

    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.


    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.
  • zoczoc Membre
    13:54 modifié #5
    Oui, les chaines constantes sont un cas très spécial... Mais il n'empêche que pour plus de clarté et de cohérence, toute chaine initialisée avec initWithString doit être releasée, même si l'implémentation interne de la chaine ne fait rien dans ce cas.


    a priori "initWithString" est implémentée de la manière suivante:


    - (NSString *) initWithString:(NSString *)aString<br />{<br />&nbsp; &nbsp; [self release];<br />&nbsp; &nbsp; return [aString retain];<br />}<br />
    


    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.
  • DjobirdDjobird Membre
    13:54 modifié #6
    D'accord merci bien.
    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 ?
  • zoczoc Membre
    13:54 modifié #7
    dans 1269195323:

    Si je fais :
    NSMutableArray* tab2 = c1.tab1;

    tab2 point bien vers la même valeur que tab1 ?

    oui, tab2 pointe sur la même instance de NSMutableArray que c1.tab1;

    Donc si j'ajoute une valeur dans tab1, la valeur sera aussi ajouter dans tab2 ?

    Oui, puisque tab1 et tab2 son un seul et unique "tableau".

    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 ?

    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.

  • DjobirdDjobird Membre
    mars 2010 modifié #8
    Bonjour,
    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 :
    &nbsp; 13 libSystem.B.dylib thread_start<br />&nbsp; 12 libSystem.B.dylib _pthread_start<br />&nbsp; 11 Foundation __NSThread__main__<br />&nbsp; 10 Foundation -[NSThread main]<br />&nbsp;  9 CarreRouge -[CarreRougeAppDelegate loadInThread] /Users/hichamennaciri/Desktop/CarreRouge/Classes/CarreRougeAppDelegate.m:53<br />&nbsp;  8 CarreRouge -[LoadingViewController compareActualAndRemotePictures] /Users/hichamennaciri/Desktop/CarreRouge/LoadingViewController.m:92<br />&nbsp;  7 CarreRouge -[LoadingViewController getPicturesListToHaveOnIphone] /Users/hichamennaciri/Desktop/CarreRouge/LoadingViewController.m:249<br />&nbsp;  6 Foundation -[NSXMLParser initWithContentsOfURL:]<br />&nbsp;  5 Foundation +[NSData(NSData) dataWithContentsOfURL:]<br />&nbsp;  4 Foundation -[NSData(NSData) initWithContentsOfURL:]<br />&nbsp;  3 Foundation -[NSData(NSData) initWithData:]<br />&nbsp;  2 Foundation -[NSConcreteData initWithBytes:length:copy:freeWhenDone:bytesAreVM:]<br />&nbsp;  1 Foundation NSAllocateObject<br />&nbsp;  0 libobjc.A.dylib _internal_class_createInstanceFromZone<br />
    


    Ce qui me ramène à  cette fonction :


    <br /><br />-(void) getPicturesListToHaveOnIphone {<br />	NSLog(@&quot;getPicturesListToHaveOnIphone&quot;);<br />	remotePicturesList = [[NSMutableArray alloc] init];<br />	NSURL *url = [[NSURL alloc] initWithString:remoteCatalogXmlFileURL];<br />	NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:url];		&nbsp; <br /><br />	[parser setDelegate:self];<br />	[parser parse];<br />	<br />	[url release];<br />	[parser release];	<br />}<br />
    


    Mais je ne comprend pas, ou c'est qu'il y a une fuite ici ?
  • ThibautThibaut Membre
    mars 2010 modifié #9
    Il y a deux [tt]release[/tt] sur la variable [tt]url[/tt].

    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.
  • DjobirdDjobird Membre
    mars 2010 modifié #10
    Pour le 2eme release pardon, c'est une faute de recopie
    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.

  • ThibautThibaut Membre
    13:54 modifié #11
    Ce ne serait pas plus simple de faire ça ?
    #define remoteCatalogXmlFileURL @&quot;http://www.siteDeDjobird.com/bird/patternCatalog.xml&quot;
    


    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).
  • zoczoc Membre
    13:54 modifié #12
    dans 1269650117:

    #define remoteCatalogXmlFileURL [[NSString stringWithString:@"http://www.siteDeDjobird.com/bird/patternCatalog.xml"]autorelease]

    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 :

    <br />// Constants.h<br />extern NSString * const remoteCatalogXmlFileURL;<br />extern NSString * const anotherConstantString;<br />// etc.<br />
    


    <br />// Constants.m<br />NSString * const remoteCatalogXmlFileURL = @&quot;http://www.siteDeDjobird.com/bird/patternCatalog.xml&quot;;<br />NSString * const anotherConstantString = @&quot;Blah&quot;;<br />
    


    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:

    <br />-(void) getPicturesListToHaveOnIphone {<br />	NSLog(@&quot;getPicturesListToHaveOnIphone&quot;);<br />&nbsp; &nbsp; &nbsp; &nbsp; [remotePicturesList release];&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  // &lt;--- Release previous array<br />	remotePicturesList = [[NSMutableArray alloc] init];<br />&nbsp; &nbsp; &nbsp; &nbsp; ....<br />
    
  • DjobirdDjobird Membre
    mars 2010 modifié #13
    Rah pardon, oui mon autorelease n'a rien à  faire la désolé.

    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 :
    <br />	NSURL *url = [[NSURL alloc] initWithString:@&quot;http://www.blabla.com/bird/patternCatalog.xml&quot; ];<br />	NSData *data = [[NSData alloc] initWithContentsOfURL:url];<br />	NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];<br />	[data release];<br />	[url release];
    


    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 ... 
  • GreensourceGreensource Membre
    13:54 modifié #14
    Tu es sur que ta fuite mémoire ne viens pas de ta variable "parser" plutôt? Parce que dans le petit bout de code que tu donnes tu ne le release pas. Mais tu n'as visiblement pas donné tout le code.

    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.
  • DjobirdDjobird Membre
    13:54 modifié #15
    Bonjour,

    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 ...) 
Connectez-vous ou Inscrivez-vous pour répondre.