Alloc ou pas ?
frederichahn
Membre
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...
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...
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
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 :
d'où plus de clarté pour le code.
.
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.
.
Je crois que je vais devoir plancher sur la doc du "Memory Management" pour mieux comprendre ces mécanismes.
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 :
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 :
Bon c'est pas KVC compliant, c'est mal mais tant pis !
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 :
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 !
ainsi que :
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 et de , 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.
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.
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...
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.
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 ?
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...
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
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.
http://developer.apple.com/documentation/Cocoa/Conceptual/Multithreading/articles/CocoaLocks.html
Ce document est important aussi.