Question(s) sur la gestion de la mémoire
Neofelis
Membre
Bonjour,
Je me présente rapidement : j'ai 28 ans, je suis en tant que professionnel développeur web (PHP5, HTML, JS...) et je commence la programmation Cocoa en tant que passion personnelle (en tout cas pour le moment ;-)). Je suis actuellement en train de lire le livre "Programmation Cocoa sous Mac OSX" d'Aaron Hilegass. J'aurai quelques questions à propos du chapitre 4 sur la gestion de la mémoire. L'auteur passe un peu vite sur quelques notions pour un débutant comme moi (en web la gestion de la mémoire est inexistante).
En gros j'ai compris comment passer du mode garbage collector au mode manuel. Maintenant l'auteur dit (page 69) : "Le code de cet ouvrage est en mode dual. Avec un tel code, les fuites de mémoire n'existent pas, que le ramasse-miettes soit activé ou non". Malheureusement l'auteur n'explique pas ce qu'est ce fameux mode dual.
Quelqu'un pourrait-t-il éclairer ma lanterne ?
Merci
Je me présente rapidement : j'ai 28 ans, je suis en tant que professionnel développeur web (PHP5, HTML, JS...) et je commence la programmation Cocoa en tant que passion personnelle (en tout cas pour le moment ;-)). Je suis actuellement en train de lire le livre "Programmation Cocoa sous Mac OSX" d'Aaron Hilegass. J'aurai quelques questions à propos du chapitre 4 sur la gestion de la mémoire. L'auteur passe un peu vite sur quelques notions pour un débutant comme moi (en web la gestion de la mémoire est inexistante).
En gros j'ai compris comment passer du mode garbage collector au mode manuel. Maintenant l'auteur dit (page 69) : "Le code de cet ouvrage est en mode dual. Avec un tel code, les fuites de mémoire n'existent pas, que le ramasse-miettes soit activé ou non". Malheureusement l'auteur n'explique pas ce qu'est ce fameux mode dual.
Quelqu'un pourrait-t-il éclairer ma lanterne ?
Merci
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
Globalement (il y a quand même quelques exceptions décrites dans les docs concernant le garbage collector), pour avoir du code "dual", il suffit d'écrire du code respectant les conventions de gestion mémoire en vigueur avant l'apparition du GC, et d'ajouter une méthode finalize.
Quand le code de la librairie sera exécuté par une application fonctionnant avec le garbage collector:
Soit le prototype de classe suivant :
et l'implémentation de la méthode description de cette même classe :
Je ne détaille pas le contexte car je ne pense pas que ce soit utile. L'auteur dit que l'appel à la méthode description provoque une fuite de mémoire, et qu'il ne faut pas utiliser [result release] car le compteur de référence passe à zéro et que l'objet qui reçoit la chaà®ne reçoit un pointeur sur un objet qui a été libéré.
Je suis ok avec le principe, ce que je ne comprends pas c'est que chez moi les appels à la méthode description continuent de fonctionner lorsque je fais un [result release] avant le return de la méthode (j'ai bien désactivé le Garbage collector). Logiquement si j'ai bien compris en faisant un [result release] vu que mon compteur de référence est à 1 je libère l'objet, donc mon appel à description ne devrait plus fonctionner...
Quelqu'un aurait une explication ?
En fait quand tu fais "release" sur ton objet result, la mémoire est libérée, c'est à dire que la zone mémoire utilisée est marquée comme de nouveau disponible pour être utilisée par un autre objet, cette zone mémoire peut être écrasée par d'autres données.
Mais le Runtime ne s'embête pas à remettre à zéro toute la zone mémoire : il marque juste l'info que la zone est libre, mais il laisse son ancien contenu pour l'instant " contenu qui pourra du coup à tout moment être remplacé par d'autres données.
Du coup quand tu récupères ces données, elles ne sont pas "sûres" mais si t'as de la chance et que la zone mémoire n'a pas été écrasée par de nouvelles données, tu arrives quand même à t'en sortir pour récupérer ce qu'il reste de données.
C'est un peu comme si tu mettais des feuilles à la poubelle, mais sans les passer à la déchiqueteuse. Tu n'es pas à l'abri que les éboueurs passent, que tu mettes d'autres trucs à la poubelle genre une bouteille qui va couler sur le papier ou quoi... mais si tu viens tout juste de mettre la feuille à la poubelle et que tu essayes de la relire avec un peu de chance elle sera encore là .
Mais tu n'as pas de garantie, et si finalement tes données ont été écrasées entre temps, tu vas utiliser des données corrompues, ou plutôt utiliser du n'importe quoi au lieu d'utiliser ce que tu crois être une NSString, et le plantage est assuré.
En bref, en mettant [result release], l'objet "result" est détruit. Après tu as genre une chance sur deux si tu récupères le pointeur dans la méthode appelante et si tu l'utilises tout de suite, de réussir à interpréter les données restantes comme une NSString, mais surtout une chance sur deux que ça crash. Et si tu mets rien qu'une ou deux instructions avant de manipuler cette zone mémoire, tu as encore plus de chances que ça crash car elle aura été réutilisée entre temps.
Soit :
soit à l'aide du setter :
Ces deux manières de libérer un objet sont-elles strictement équivalentes ?
La bonne manière de libérer une variable d'instance est d'employer release.
En écrivant la valeur nil dans la variable, tu ne libères pas la zone mémoire allouée. Et hop, une fuite mémoire !
Les exemples de ton livre sont certainement corrects, mais doivent concerner des variables définies comme propriétés avec l'attribut RETAIN. C'est différent parce que la propriété gère elle-même l'allocation mémoire.
En stockant un objet dans une propriété RETAIN, elle s'occupe automatiquement de libérer le contenu précédent. Magique !
Un détail important : il ne faut pas oublier le self avant d'accéder à la variable dans une méthode de sa classe. Sinon le programme accède directement au contenu de la variable sans passer par le code assurant la gestion mémoire.
Oui et Non, parce que dans son exemple, il appelle le setter, il n'affecte pas nil directement à la variable. Et si le setter est implémenté correctement (ou s'il est synthétisé à partir d'une property avec l'attribut retain), il est sensé faire un release sur la valeur précédente de la variable...
Je rappelle que 'self.toto = nil' est équivalent à '[self setToto: nil]' (par contre, effectivement c'est très différent de 'toto = nil' ou 'self->toto = nil', qui, eux, sont également équivalents, même si en pratique la deuxième écriture n'est pas utilisée ).
Effectivement le setter en question fait un release de l'ancienne valeur et un retain de la nouvelle.
Donc dans ce cas visiblement le resultat est le même. Par contre je ne vois pas bien l'intérêt de cette syntaxe (mettre nil à l'aide du setter) puisqu'à priori le résultat est le même qu'avec release, et je trouve la syntaxe "release" plus explicite, plus simple et plus lisible.
L'intérêt est justement dans ce que tu décrivais au début !
Si tu release une variable ton pointeur est toujours là et il ne pointe plus sur rien. Si tu l'appelles tu aura un Crash pour avoir lancé une méthode sur un objet ou une variable qui n'existe plus..
Si tu l'as mis = nil alors tu auras une valeur interprétable (0 ou nil selon ce que tu auras appelé).
En général c'est en effet plus simple de faire juste release quand tu es dans le dealloc vu que de toute façon il n'y a pas de risque que ta variable soit réappelee après ce dealloc ; moi je préfère souvent et trouve cela plus explicite.
D'autant plus qu'appeler le setter va émettre les messages de KVO dont on peut se passer dans le cadre d'un dealloc
Mais d'un autre côté, quand on libère également la mémoire mais dans le cadre d'un viewDidUnload (cas de memory warning sur l'appli) là c'est important de remettre la variable à nil après le release (et du coup autant appeler le setter plutôt que de faire release + un "=Nil") ! Car sinon l'instance de ta classe existe encore et peut accedder à ta variable d'instance... alors que tu l'as releasee et si tu l'as pas mis à nil au passage, crash assuré
Du coup par habitude on met parfois la même chose dans le dealloc (ce qui permet de faire aussi un copier/coller... hum)
De même, avec le modern runtime, maintenant on peut déclarer des @property sans déclarer de variable d'instance associée (backing variable), du coup dans ce cas tu n'as pas le choix, la variable d'instance n'existant pas en tant que tel tue peux pas y accéder par code (en réalité si mais bon faut savoir comment le compilo l'a nommé et tu ne peux le faire que après le @synthesize et de toute façon ça serait pas trop cohérent de l'utiliser plutôt que la @property alors que dans le .h elle n'existe même pas officiellement... bref) du coup dans le dealloc là tu es bien obligé de faire self.mapropriete=nil vu que tu n'as pas de variable d'instance sur laquelle faire de release !
Pour éviter le copier coller, je fais toujours une méthode [tt]clean[/tt] dans mes controllers qui est appelé depuis le dealloc et le didunload. Même si en théorie, on n'est pas obligé de tout libérer dans le viewdidunload.
L'idéal est de faire une méthode cleanOutlets qui remet à nil tous les IBOutlets, après avoir fait un release sur ceux qui sont en @property(retain), et release et remet à nil également tous les objets crées et retenus dans le viewDidLoad puisqu'ils seront recrées quand la vue sera de nouveau chargée plus tard après la réception du memorywarning.
Mais viewDidUnload ne doit pas releaser les objets du modèle par contre.
Apres il y a plein née façons de faire. Par exemple s'il faut bien faire un release sur v1 que ce soit dans le cas de viewDidUnload ou le cas de dealloc, par contre la remise à nil de ces IBOutlets n'est utile que dans viewDidUnload. Par exemple on pourrait déplacer le code de v2=nil dans viewDidUnload au lieu de clean. Mais bon, pour ce que ça coûte ded remettre la variable à nil, ça serait bête de se priver de cette sécurité dans tous les cas.