Alloc ou pas ?

frederichahnfrederichahn Membre
20:43 modifié dans API AppKit #1
Je vous préviens, je suis débutant en programmation orientée objet. Certains vont probablement rire en lisant ma question...

Pour faire bref, pourquoi faut-il un alloc dans le premier exemple, et pas dans le 2e ?

1. exemple = [[NSMutableDictionary alloc] initWithObjects:values forKeys:keys];
2. exemple = [NSString string]

Bon, je comprends bien que ce sont deux cas totalement différents, mais dans quel cas faut-il allouer la mémoire avec Alloc, et dans quel cas n'est-ce pas nécessaire ?

Merci...

Réponses

  • BruBru Membre
    juillet 2007 modifié #2
    La seconde méthode est une méthode "convéniente" (terminologie Apple).
    Ces méthodes font le alloc puis le init (et enfin un autorelease) en un seul appel.

    Généralement, on utilise ces méthodes quand on ne veut pas se soucier de la destruction de l'objet.
    Par exemple, si tu veux mettre un NSString dans un NSArray, normalement tu devrais faire :
    NSString *s;<br />s=[[NSString alloc] initWithString:@&quot;toto&quot;];<br />[array addObject:s];<br />[s release];<br />[/code]<br /><br />Mais avec une méthode convéniente :<br />[code]<br />[array addObject:[NSString stringWithString:@&quot;toto&quot;]];<br />
    


    d'où plus de clarté pour le code.

    .
  • frederichahnfrederichahn Membre
    20:43 modifié #3
    Merci Bru pour cette explication très claire, je comprends mieux à  présent. Mais il subsiste un petit mystère : dans ton exemple, tu crées un NSString que tu ajoutes à  un tableau, puis tu le supprimes avec un release. Est-ce que le fait d'avoir rajouté un NSString au tableau en fait une copie ?
  • BruBru Membre
    20:43 modifié #4
    dans 1184654764:

    Mais il subsiste un petit mystère : dans ton exemple, tu crées un NSString que tu ajoutes à  un tableau, puis tu le supprimes avec un release. Est-ce que le fait d'avoir rajouté un NSString au tableau en fait une copie ?


    Non, pas de copie.

    En fait, il s'agit de savoir comment la gestion en mémoire des objets cocoa se fait :
    Chaque objet possède dans sa structure un compteur de référence.
    Ce compteur est mis à  1 à  la création de l'objet (alloc), et peut être augmenté via la méthode retain.
    Ce compteur peut être décrémenté via la méthode release. Lorsqu'il tombe à  0, l'objet est détruit.

    Dans l'exemple que j'ai donné : le NSString créé a donc un compteur à  1.
    Le fait de l'ajouter dans le NSArray provoque un retain implicite (en effet, le tableau "retient" cet objet). Le compteur passe donc à  2.
    Enfin, le release suivant remet le compteur à  1.

    Ainsi, lorsque (plus tard) l'objet sera retiré du tableau, ou lorsque le tableau sera lui-même détruit, un release implicite sera fait : le compteur du NSString passera à  0, et donc l'objet sera détruit.

    .
  • frederichahnfrederichahn Membre
    20:43 modifié #5
    Merci infiniment Bru !

    Je crois que je vais devoir plancher sur la doc du "Memory Management" pour mieux comprendre ces mécanismes.
  • MulotMulot Membre
    20:43 modifié #6
    Si je peux essayer de t'aider, j'ai aussi eu quelques soucis avec cette gestion de la mémoire.

    En fait, quand un objet ne m'était plus nécessaire, je voulais le supprimer, sans me soucier si il était utilisé ailleurs, par un autre objet.

    Le retainCount de l'objet correspond en fait aux dépendances que d'autres objets ont sur lui.

    Imaginons que tu ais des objets de type Personne: un prénom (NSString) et une date de naissance (NSDate).

    Dans ton constructeur, les attributs sont supposés être retenus. Par exemple :

    <br /><br />- (id) initWithFirstName: (NSString*) name andBirthday: (NSDate) birthDay {<br /><br />//appel de l&#39;initialiseur désigné de la super classe<br />if(self = [super init]){<br /><br />//personName est un attribut declare dans le fichier header<br />&nbsp; personName = [name retain];<br /><br />&nbsp; personBirthday = [birthDay retain];<br /><br />&nbsp; return self;<br /><br />}<br /><br />return nil;<br /><br />}<br /><br />
    


    Ici dans ce constructeur retient les objets qui ont "servit" à  construire le nouvel objet, pour augmenter de 1 le retainCount de chacun des deux paramètres.

    Pour essayer d'illustrer ceci, on va supposer avoir des accesseurs dans la classe Personne du genre :

    <br /><br />- (NSString¨) birthday {<br /><br />return personBirthday;<br /><br />}<br />
    


    Bon c'est pas KVC compliant, c'est mal mais tant pis ! :p

    Supposons que tu ai un premier objet Personne créé, et que les deux attributs qu'il possède on en retainCount de 1.

    Imaginons ensuite, que tu veuilles créer un nouvel objet Personne, mais que celui-ci est né le même jour que le précédent.

    Une façon rapide et simple de le faire serait de faire :

    <br /><br />Personne * nouvellePersonne = [[Personne alloc] initWithFirstName:@&quot;Mon nom&quot;<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; andBirthday: [anciennePersonne birhtday]];<br /><br />
    


    Ici, on passe une chaà®ne statique en paramètre pour le nom, chaine qui se verra allouer un message retain dans le constructeur, et qui ne sera donc pas libérée tant que l'objet l'ayant créé sera opérationnel (si cet objet n'est pas retenu ailleurs).

    Maintenant c'est le second paramètre qui nous interesse, on lui retourne en fait l'objet personBirthday de l'autre Personne, le retain count de l'objet en paramètre (retourné ici par la méthode) était de 1, et il passe à  2 suite au retain dans le constructeur.

    Concrètement, il n'y aura qu'un seul objet personBirthday, mais il sera nécessaire à  deux objets, d'où son retainCount de 2.

    Si un des deux objets Personne est désaloué, son retainCount passera à  1.

    En espérant avoir été clair ( ce qui n'est jamais mon cas !), si tu as des questions n'hésites pas, j'ai aussi eu des soucis avec cette partie.

    Bon courage !
  • frederichahnfrederichahn Membre
    20:43 modifié #7
    Considérons l'accesseur suivant :

    <br />- (void)setBirthdate:(NSCalendarDate *)newDate<br />{<br />&nbsp; &nbsp; [newDate retain];<br />&nbsp; &nbsp; [birthdate release];<br />&nbsp; &nbsp; birthdate = newDate;<br />}<br />
    


    ainsi que :

    <br />- (NSCalendarDate *)birthdate<br />{<br />&nbsp; &nbsp; return birthdate;<br />}<br />
    


    Au niveau de l'accesseur setBirthdate, l'ancienne date pointée par birthdate est relâchée AVANT que la nouvelle date soit affectée. Cela voudrait dire que pendant un très court instant, entre l'exécution de
    [birthdate release];
    
    et de
    birthdate = newDate;
    
    , si quelqu'un fait appel à  l'accesseur birthdate alors celui retournera l'adresse d'un objet qui a été relaché.

    Vous voyez ce que je veux dire ?

    Bon, après tout c'est une question de probabilité. Vu que le code est exécuté très rapidement, ce cas ne devrait pas se produire souvent. Mais j'ai appris avec l'expérience que pour être un bon développeur, il faut toujours tenir compte de toutes les situations possibles et écarter les sources de bugs indécelables.

    PS. Je viens de recevoir mon exemplaire de Cocoa Programming for Mac OS X (second edition) de Aaron Hillegass. Il est franchement bien foutu.
  • juillet 2007 modifié #8
    Tu soulèves un point intéressant, mais la probabilité que ce que tu évoques est très faible: n'oublie pas que même en faisant du multithread un processeur ne sait jamais faire qu'une chose à  la fois. Il y a les multiprocesseurs tu vas me dire, mais ce n'est pas parce que ton système a plusieurs processeurs que ton code est forcément multithreadée. Si tu codes une appli multithreadée, là  c'est différent et tu dois normalement mettre des "verrous" pour éviter de genre de déconvenues.

    Maintenant ce genre de setter peut causer des problèmes, même sur du code en pur monothread.  Tu exécutes une méthode et au début tu prends la valeur de birthdate. Conformément à  la règle usuelle de gestion de la mémoire, tu la retiens pas. Puis le code continue, et _birthdate est modifié et l'objet qui y étais initialement rattaché est relaché. Mais le pointeur créé en début de méthode pointe toujours vers cet espace mémoire, donc plantage si on tente d'appeler la référence "initiale".

    C'est pour éviter ce genre de problème que certains préfèrent reporter le dealloc* en mettant un autorelease au lieu du release dans le setter ou que d'autres écrivent leur getter comme ceci:
    [tt]-(NSDate*)birthdate { return [[_birthDate retain] autorelease]; }[/tt]


    *il faut arrêter de penser "release = relacher". Relacher, c'est dealloc et c'est une méthode qu'il ne faut jamais appeler à  la main. release ça veut dire "décrémenter le compteur de référence", ce qui a parfois pour conséquence de relacher l'objet. La nuance est importante.
  • schlumschlum Membre
    juillet 2007 modifié #9
    dans 1185663676:

    Maintenant ce genre de setter peut causer des problèmes, même sur du code en pur monothread.  Tu exécutes une méthode et au début tu prends la valeur de birthdate. Conformément à  la règle usuelle de gestion de la mémoire, tu la retiens pas. Puis le code continue, et _birthdate est modifié et l'objet qui y étais initialement rattaché est relaché. Mais le pointeur créé en début de méthode pointe toujours vers cet espace mémoire, donc plantage si on tente d'appeler la référence "initiale".


    Là  je ne suis point d'accord... Conformément à  la règle usuelle de gestion de la mémoire, si on a besoin de l'objet un certain temps, on lui mets un "retain" (ex : ajout à  un NSMutableArray...)

    On ne lui mets pas de retain que si on en a besoin dans le même "scope" (et évidemment, il faut éviter d'y faire un "set" avant d'en avoir fini avec :P)

    Ce problème n'est pas inhérent au setter, mais à  la programmation qu'il y a au dessus.


    PS : release veut dire relâcher en traduction littérale, mais ce n'est effectivement pas la mémoire qu'on relâche, mais le "lien" qu'on avait posé sur l'objet...
  • schlumschlum Membre
    20:43 modifié #10
    dans 1185661905:

    Bon, après tout c'est une question de probabilité. Vu que le code est exécuté très rapidement, ce cas ne devrait pas se produire souvent. Mais j'ai appris avec l'expérience que pour être un bon développeur, il faut toujours tenir compte de toutes les situations possibles et écarter les sources de bugs indécelables.


    Tu peux prévenir ça en mettant des @synchronized dans tous tes setters, mais... je pense que si ce problème peut se produire, c'est qu'il y a un problème au dessus au niveau de la conception du programme.

    Penser qu'un thread peut demander une valeur au moment ou un autre thread la change ?? En général ce genre de cas est géré à  un niveau supérieur au setter par des mutex.
  • frederichahnfrederichahn Membre
    20:43 modifié #11
    Renaud : pourquoi as-tu écrit " _birthdate " avec un underscore ? J'ai déjà  vu cela dans plusieurs code source, mais je n'ai jamais lu d'explications là  dessus.

    schlum : C'est quoi un " @synchronized " ? Ca sert à  quoi et ça s'utilise comment ? C'est quoi des mutex ?




    Pour en revenir à  mon exemple : est-ce qu'un autorelease à  la place d'un release dans le setter ne serait tout simplement pas la solution ?

    Est-ce qu'on peut toujours utiliser des autorelease ? Si le code qui va appeler mon setter n'a pas créé un NSAutoreleasePool (un code qui n'est pas de moi, mais du système), est-ce cela génèrera une exception ?
  • schlumschlum Membre
    20:43 modifié #12
    dans 1185719219:

    Renaud : pourquoi as-tu écrit " _birthdate " avec un underscore ? J'ai déjà  vu cela dans plusieurs code source, mais je n'ai jamais lu d'explications là  dessus.


    Certains programmeurs (dont ceux d'Apple si je ne m'abuse) mettent des _ devant toutes les variables de classes pour ne pas les confondre avec les autres variables...

    schlum : C'est quoi un " @synchronized " ? Ca sert à  quoi et ça s'utilise comment ? C'est quoi des mutex ?


    C'est une portion de code qui s'exécute d'un seul tenant...
    http://fr.wikipedia.org/wiki/Exclusion_mutuelle
    Voir aussi la documentation de NSLock


    Pour en revenir à  mon exemple : est-ce qu'un autorelease à  la place d'un release dans le setter ne serait tout simplement pas la solution ?

    Est-ce qu'on peut toujours utiliser des autorelease ? Si le code qui va appeler mon setter n'a pas créé un NSAutoreleasePool (un code qui n'est pas de moi, mais du système), est-ce cela génèrera une exception ?


    Les "autorelease", c'est assez lourd... Je préfère ne l'utiliser que quand les autres solutions sont trop contraignantes ou quand on a pas le choix (constructeur de commodité imposé...). Je ne pense pas que ça ait sa place dans un setter.

    Quand un thread sans NSAutoReleasePool appelle un "autorelease", ça fait un "leak" et un Warning dans la console, pas d'exception.
Connectez-vous ou Inscrivez-vous pour répondre.