retainCount et collection d'objets

Philippe49Philippe49 Membre
août 2008 modifié dans API AppKit #1
On sait bien qu'ajouter un objet dans un NSArray augmente son retainCount de 1  >:) .
On sait également que lors de la désallocation de ce dit tableau, les objets voient leurs retainCount être décrémentés  >:) .

Maintenant si on fait un retain sur un tableau, ou un release sans que le tableau soit désalloué, les retainCount des objets ne sont pas changés.

[size=12pt]Expérience:[/size]
#import &lt;Cocoa/Cocoa.h&gt;<br /><br />int main (int argc, const char * argv&#91;]) {<br />	NSAutoreleasePool * pool=[[NSAutoreleasePool alloc] init];<br />	NSButton * obj1=[[[NSButton alloc] initWithFrame:NSZeroRect] autorelease]; <br /><br />	// Après la création des deux objets <br />	NSLog(@&quot;&#092;rAprès la création de l&#39;objet obj1 (en mode autorelease), il est retenu&nbsp; %u fois&#092;n&quot;,[obj1 retainCount]);<br /><br />	// On crée un tableau avec les deux objets <br />	NSArray * array1=[[NSArray alloc] initWithObjects:obj1,nil] ;<br />	NSLog(@&quot;&#092;rAprès la création du premier tableau , il est retenu&nbsp; %u fois&#092;n&quot;,[obj1 retainCount]);<br />	<br />	// On copie le tableau dans un autre tableau<br />	NSArray * array2=[NSArray arrayWithArray:array1];<br />	NSLog(@&quot;&#092;rAprès la copie du tableau dans un deuxième tableau, obj1 est retenu&nbsp; %u fois&#092;n&quot;,[obj1 retainCount]);<br /><br />	// les tableaux sont différents, les objets sont les mêmes<br />	NSLog(array1==array2 ? @&quot;&#092;r array1=array2&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &quot; : @&quot;&#092;rarray1 ≠ array2&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &quot;);<br />	NSLog([array1 objectAtIndex:0]==[array2 objectAtIndex:0] ? @&quot;&#092;rpremier objet de array1=premier objet de array2&#092;n&quot; : @&quot;&#092;rpremier objet de array1 ≠ premier objet de array2&#092;n&quot;);<br /><br />	// Le retain sur le tableau n&#39;affecte pas le retainCount de chaque objet<br />	[array1 retain];<br />	NSLog(@&quot;&#092;rAprès retain sur le premier tableau, obj1 est toujours retenu&nbsp; %u fois, , sans changement donc&#092;n&quot;,[obj1 retainCount]);<br /><br />	// Le releaase sur le tableau n&#39;affecte le retainCount de chaque objet que par disparition<br />	[array1 release];<br />	NSLog(@&quot;&#092;rAprès un release sur l&#39;un des tableaux, obj1 est retenu&nbsp; %u fois, sans changement donc&#092;n&quot;,[obj1 retainCount]);<br /><br />	[array1 release];<br />	NSLog(@&quot;&#092;rLors de la désallocation d&#39;un des tableaux, obj1 est retenu&nbsp; %u fois, avec changement cette fois&quot;,[obj1 retainCount]);	<br />	<br />	[pool drain];<br />	return 0;<br />}


[size=12pt]Résultat :[/size]
% gcc pgm.m -o pgm -framework Cocoa
% pgm
Après la création de l'objet obj1 (en mode autorelease), il est retenu  1 fois
Après la création du premier tableau , il est retenu  2 fois
Après la copie du tableau dans un deuxième tableau, obj1 est retenu  3 fois
array1 ≠ array2                                      
premier objet de array1=premier objet de array2
Après retain sur le premier tableau, obj1 est toujours retenu  3 fois, , sans changement donc
Après un release sur l'un des tableaux, obj1 est retenu  3 fois, sans changement donc
Lors de la désallocation d'un des tableaux, obj1 est retenu  2 fois, avec changement cette fois
%


[size=12pt]Conséquence :[/size]
Un NSArray mal désalloué ==> tous les objets de ce NSArray ont un retainCount faux.

Réponses

  • fouffouf Membre
    12:26 modifié #2
    Je ne trouve pas ce comportement anormal. Ca marche exactement comme prévu. Et le fait que le retain count soit faux lorsque l'objet est "possédé" par une collection n'est pas particulier aux collections justement. Si tu fais la même chose avec une classe modèle qui contient comme variable d'instance une NSString que l'objet "retient" et puis si tu copies le modèle, tu le retiens et tu le relache deux fois, a la fin on aura le même résultat que pour ton array.

    En tous cas, excellent exemple pour mettre en évidence la nécessité d'une bonne gestion de la mémoire ;)
  • NoNo Membre
    août 2008 modifié #3
    dans 1217926355:

    Lors de la désallocation d'un des tableau, obj1 est retenu  2 fois, avec changement cette fois


    Je ne vois rien d'anormal.
    Le refCount de obj1 est à  tout moment valide.

    Il vaut 2 à  la fin, car obj1 est bien retenu 2 fois : par array2, et par le releasePool (via le init suivi du autorelease).

    Le refCount, peut s'assimiler au nombre de dépendance que l'objet a vis à  vis de son environnement (voir l'analogie avec la laisse de chien de Hillegass).
    Modifier le refCount d'une collection ne doit pas modifier les refCounts des objets contenus, car il n'y a pas de modification du nombre de dépendance de ces derniers.

    PS : ce n'est pas une réponse à  Philippe dont l'exemple est caractéristique (cas d'école), mais plutôt une précision pour Ancrou (cf l'autre topic).
  • août 2008 modifié #4
    Sans parler des problèmes de perfs !

    Question : pourquoi ne pas utiliser la magnifique application qu'est xCode pour compiler ?

    Pourquoi des \r et \n dans les log ??

    Pourquoi ne pas faire simple ?

    NSLog([array1 objectAtIndex:0]==[array2 objectAtIndex:0] ? @\rpremier objet de array1=premier objet de array2\n : @\rpremier objet de array1 ≠ premier objet de array2\n);


    Serait plutôt : (on évite d'écrire deux fois la même chaine de caractère !)

    NSLog(@premier objet de array1 %@ premier objet de array2",[array1 objectAtIndex:0]==[array2 objectAtIndex:0]?@=:@≠);


  • schlumschlum Membre
    12:26 modifié #5
    dans 1217926355:

    [size=12pt]Conséquence :[/size]
    Un NSArray mal désalloué ==> tous les objets de ce NSArray ont un retainCount faux.


    Ben... Oui, encore heureux  :o
  • Philippe49Philippe49 Membre
    12:26 modifié #6
    En résumé, ce post aura eu le mérite ... d'attirer l'attention sur des portes ouvertes ...  :)


  • veveveve Membre
    12:26 modifié #7
    moi pas comprendre j'obtiens ça
    <br />2008-08-07 01:38:31.938 memoire_objective-cocoa.org[1230:10b] <br /> Après la création de l&#39;objet obj1 (en mode autorelease), il est retenu&nbsp; 2147483647 fois<br />2008-08-07 01:38:31.971 memoire_objective-cocoa.org[1230:10b] <br /> Après la création du premier tableau , il est retenu&nbsp; 2147483647 fois<br />2008-08-07 01:38:31.973 memoire_objective-cocoa.org[1230:10b] <br /> Après la copie du tableau dans un deuxième tableau, obj1 est retenu&nbsp; 2147483647 fois<br />2008-08-07 01:38:31.973 memoire_objective-cocoa.org[1230:10b] <br /> array1 ≠ array2<br />2008-08-07 01:38:31.974 memoire_objective-cocoa.org[1230:10b] <br /> Premier objet de array1 = premier objet de array2<br />2008-08-07 01:38:31.974 memoire_objective-cocoa.org[1230:10b] <br /> Après retain sur le premier tableau, obj1 est toujours retenu&nbsp; 2147483647 fois, donc sans changement <br />2008-08-07 01:38:31.975 memoire_objective-cocoa.org[1230:10b] <br /> Après un release sur l&#39;un des tableaux, obj1 est retenu&nbsp; 2147483647 fois, sans changement donc<br />2008-08-07 01:38:31.975 memoire_objective-cocoa.org[1230:10b] <br /> Lors de la désallocation d&#39;un des tableaux, obj1 est retenu&nbsp; 2147483647 fois, avec changement cette fois
    


  • 12:26 modifié #8
    Parce que les chaines sont déclarées en static !

    Le retain count vaut max int (au pifomètre) : c'est un cas particulier.

    Faire un NSString stringWithString:... mais si c'est juste pour faire la copie d'une chaine static...
  • Philippe49Philippe49 Membre
    12:26 modifié #9
    dans 1217967733:

    Question : pourquoi ne pas utiliser la magnifique application qu'est xCode pour compiler ?

    Pour enfoncer un clou, on prend un marteau, pour arracher un arbre, on prend une pelle mécanique
    Pour compiler xCode utilise une ligne de commande avec gcc 10 fois plus compliquée. voir ici

    dans 1217967733:

    Pourquoi des \r et \n dans les log ??

    La présentation de Veve avec toutes les "2008-08-07 01:38:31.975 memoire_objective-cocoa.org[1230:10b]" donnent la réponse à  cette question.

    dans 1217967733:

    Pourquoi ne pas faire simple ?

    En quoi ce code est compliqué ?
  • Philippe49Philippe49 Membre
    août 2008 modifié #10
    dans 1218066492:

    moi pas comprendre j'obtiens ça

    C'est ce que te dis Supermic, tu as du mettre une chaà®ne constante comme objet, et le retain count dans ce cas est NSIntegerMax. Un retain ou un release sur un objet ayant ce retainCount est sans effet :

    [size=12pt]Exemple[/size]
    #import <Foundation/Foundation.h>
    int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool=[[NSAutoreleasePool alloc] init];
    NSString * str=@Coucou;
    NSLog(@\r%u                                                        ,[str retainCount]);
    [str retain];
    NSLog(@\r%u                                                        ,[str retainCount]);
    [str release];
    NSLog(@\r%u                                                        ,[str retainCount]);
    [pool drain];
    return 0;
    }

    [size=12pt]Exécution[/size]
    % gcc pgm.m -o pgm -framework Foundation
    % pgm
    2147483647                                                       
    2147483647
    2147483647                                                       
    %


  • août 2008 modifié #11
    2. As tu déjà  utilisé l'application Console.app pour afficher les log de ton programme ? Est ce que tu les as vu justement ? Ou alors c'est pelle mécanique pour planter un clou ? :p Comment faire des applications multi-langues, en UB, etc... ?

    1 et 3. Sais pas... c'est histoire d'être "moderne"... mais c'est vrai, nous ne sommes qu'en 2008... tu préfères rouler en 104 ou en 307 ? Pourquoi ne pas utiliser printf alors ? Foir directement écrire sur le stdout ?

    4. NSIntegerMax tu as vérifié dans le doc ?

  • AliGatorAliGator Membre, Modérateur
    août 2008 modifié #12
    dans 1218090340:

    2. As tu déjà  utilisé l'application Console.app pour afficher les log de ton programme ? Est ce que tu les as vu justement ? Ou alors c'est pelle mécanique pour planter un clou ? :p Comment faire des applications multi-langues, en UB, etc... ?

    1 et 3. Sais pas... c'est histoire d'être "moderne"... mais c'est vrai, nous ne sommes qu'en 2008... tu préfères rouler en 104 ou en 307 ? Pourquoi ne pas utiliser printf alors ? Foir directement écrire sur le stdout ?

    4. NSIntegerMax tu as vérifié dans le doc ?


    Oulà  du calme Supermic, j'ai l'impression que le ton monte, keep cool, c'est l'été  ;)
    C'est quoi les points 1. 2. 3. 4. auquel tu réponds ? (pourquoi ne pas avoir utilisé les [ quote ] pour rendre ton post plus clair ? enfin je dis ça... juste histoire d'être moderne hein ;D) Et heu j'ai rien compris de ce que venait faire là  le coup des applications localisées ou en UB ?!

    Sinon pour NSIntegerMax, c'est indiqué dans la doc ceci :
    For objects that never get released (that is, their release method does nothing), this method should return UINT_MAX, as defined in <limits.h>.
    C'est vrai que NSIntegerMax vaut LONG_MAX et pas UINT_MAX mais bon le principal c'était l'idée, à  savoir que pour les objets non autoreleasés comme les NSString statiques, ils ont un retainCount qui vaut une valeur limite spéciale, et que dans ce cas particulier retain et release n'ont aucun effet alors sur leur retainCount.
  • Philippe49Philippe49 Membre
    12:26 modifié #13
    Pour ce qui concerne Console.App, que l'on passe par le terminal ou XCode, c'est du pareil au même pour le NSLog.
    On peut même signaler qu'en mode Release, les NSLog qui resteraient dans le code s'affichent dans ce log.

    printf=fprint(sdout), NSLog ça serait plutôt fprintf(stderr). Mais tu as raison, on pourrait remplacer le NSLog par un printf dans ce code. Ceci dit, si on veut garder propre la Console, il faudrait ne jamais utiliser NSLog, même dans XCode.


    dans 1218090340:

    4. NSIntegerMax tu as vérifié dans le doc ?

    Il me semble que je suis tombé dessus il y a quelques semaines, et que j'avais fait déjà  l'essai que j'indiques au post précédent, avec une boucle même de retain pour voir ce qui se passe si on fait un très grand nombre de retain sur un objet  :). J'avoue ne pas avoir et ne pas avoir eu le courage de revérifier, mais cela me semble si logique en C que ce serait pour moi une surprise que cela soit autrement, sauf réglage spécial évidemment.

    dans 1218095910:


    Sinon pour NSIntegerMax, c'est indiqué dans la doc ceci :
    For objects that never get released (that is, their release method does nothing), this method should return UINT_MAX, as defined in <limits.h>.
    C'est vrai que

    Merci Ali de calmer le jeu.
    Ce qui est marrant dans l'histoire  :), et qui confirme ce que tu dis "le principal ici c'était l'idée" énoncée en premier lieu par Supermic dans son premier post, c'est que la doc est ici en contradiction avec l'expérimentation, UINT_MAX c'est 4 milliards et quelques , ce qui est affiché dans l'expérience, c'est INT_MAX.


  • schlumschlum Membre
    12:26 modifié #14
    C'est valable pour les singletons aussi (tous les objets répondant à  la méthode de classe "shared...")
  • août 2008 modifié #15
    On va encore me faire passser pour un méchant très agressif mais schlum tu as essayé (au hasard) : NSLog(@la %d,[[NSWorkspace sharedWorkspace] retainCount]); ? ;)
  • schlumschlum Membre
    12:26 modifié #16
    Ah ben après s'ils font " faites ce que je dis, pas ce que je fais ", j'y peux rien...

    http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaFundamentals/CocoaObjects/chapter_3_section_10.html
  • veveveve Membre
    août 2008 modifié #17
    dans 1218066904:

    Parce que les chaines sont déclarées en static !

    Le retain count vaut max int (au pifomètre) : c'est un cas particulier.

    Faire un NSString stringWithString:... mais si c'est juste pour faire la copie d'une chaine static...


    oui, c'était bien à  cause des chaà®ne mais j'ai des chaà®nes statique (ou modifiables d'ailleurs) parce que je n'ai pus compiler avec le bouton (NSButton) : j'ai eu  une erreur.

    Undefined symbols:<br />&nbsp; &quot;.objc_class_name_NSButton&quot;, referenced from:<br />&nbsp; &nbsp; &nbsp; literal-pointer@__OBJC@__cls_refs@NSButton in memoire_objective-cocoa.org.o<br />ld: symbol(s) not found<br />collect2: ld returned 1 exit status
    


    J'avais créer un nouveau projet pour cet exemple avec Xcode 3.1 (SDK Iphone) un console application.
  • Philippe49Philippe49 Membre
    12:26 modifié #18
    dans 1218459337:

    oui, c'était bien à  cause des chaà®ne mais j'ai des chaà®nes statique (ou modifiables d'ailleurs)

    Il y a confusion sur le mot static. En C, et donc en Objective-C, le qualificateur static n'a rien à  voir avec const. C'est une question de durée de vie de la variable.
    [size=12pt]Exemple :[/size]
    #import <Foundation/Foundation.h>
    int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool=[[NSAutoreleasePool alloc] init];
    static NSString * string=nil;
    string=[NSString stringWithFormat:@Hello World %d,2006];
    NSLog(@\rPour une chaà®ne déclarée en dynamique, le retain count de la chaà®ne est %u,[string retainCount]);
    string=@Coucou;
    NSLog(@\rPour une chaà®ne déclarée par référence à  une chaà®ne constante, le retain count de la chaà®ne est %u,[string retainCount]);
    [pool drain];
    return 0;
    }

    % gcc pgm.m -o pgm -framework Foundation
    % pgm
    Pour une chaà®ne déclarée en dynamique, le retain count de la chaà®ne est 1
    Pour une chaà®ne déclarée par référence à  une chaà®ne constante, le retain count de la chaà®ne est 2147483647
    %
  • Philippe49Philippe49 Membre
    août 2008 modifié #19
    Voici un exemple tiré de la doc Apple pour voir ce que signifie static.

    NSManagedObjectContext * managedObjectContext(){
       static NSManagedObjectContext * moc=nil;
       if(moc) {
          return moc;
       }
       moc=[[NSManagedObjectContext alloc] init];
       // ... implementation à  compléter
       return moc;
    }

    La variable moc est créée une fois et une seule lors du premier appel de la fonction, valant initialement nil. Comme au premier appel elle vaut nil, on passe aux instructions d'initialisation.
    Lors d'un prochain appel de la fonction  moc n'est plus nil puisqu'il a été initialisé au premier appel, et la valeur de moc est directement renvoyée.


  • veveveve Membre
    12:26 modifié #20
    dans 1218460503:

    dans 1218459337:

    oui, c'était bien à  cause des chaà®ne mais j'ai des chaà®nes statique (ou modifiables d'ailleurs)

    Il y a confusion sur le mot static. En C, et donc en Objective-C, le qualificateur static n'a rien à  voir avec const. C'est une question de durée de vie de la variable.
    [


    je comprend tres bien cela !

    pardon mais je souhaité savoir pourquoi je n'arrivais pas compiler avec le NSButton.
  • Philippe49Philippe49 Membre
    août 2008 modifié #21
    dans 1218490858:

    pardon mais je souhaité savoir pourquoi je n'arrivais pas compiler avec le NSButton.


    Tu as du choisir un Template qui ne contenait pas le framework AppKit, framework qui contient la classe NSButton.

    Tu parles plus haut de "Console Application" , je ne connais pas dans XCode 3.1, sans doute est-ce une Foundation Tool ou peut-être standard Tool que tu as pris ? Ces projets ne contiennent pas au départ que le framework Foundation dans lequel ne se trouve pas NSButton. Dans ce cas, il aurait été possible d'ajouter le framework Cocoa. 
  • veveveve Membre
    12:26 modifié #22
    oui mais j'ai remplacé <Foundation/Foundation.h> par <Cocoa/Cocoa.h>dans .m et .pch (fichier pour précompilation).

    et c'est en faite un "Foundation Tool" !!  ::)
  • Philippe49Philippe49 Membre
    12:26 modifié #23
    Cela ne sufit pas dans un projet XCode.Il faut aussi ajouter la référence  framework.
    Regarde dans le group framework, tu ne dois avoir que le framework Foundation. Cela veut dire que l'option de compilation ne sera que -framework Foundation et non -framework Cocoa, et par conséquent, le #import <Cocoa/Cocoa.h> ne pourra pas être réalisé.

    • Faire un clic-droit sur le group "Externals Frameworks And Libraries" (pour que ton projet reste bien rangé on le met là )
    • Choisir Add Existing Framework
    • Le panel doit s'ouvrir dans /Systeme/Library/Frameworks
    • Choisir Cocoa.framework

    On peut alors supprimer Foundation (sélectionner et effacer) car Cocoa = Foundation+AppKit
  • schlumschlum Membre
    12:26 modifié #24
    dans 1218569994:

    oui mais j'ai remplacé <Foundation/Foundation.h> par <Cocoa/Cocoa.h>dans .m et .pch (fichier pour précompilation).

    et c'est en faite un "Foundation Tool" !!  ::)


    Tu dois tomber sur une erreur avec cet include non ?
    Si le Framework n'a pas été ajouté au projet il ne risque pas de trouver le header.
  • veveveve Membre
    12:26 modifié #25
    :o ça marche merci à  tous !!
Connectez-vous ou Inscrivez-vous pour répondre.