122 votes

Comprendre le comptage de références avec Cocoa et Objective-C

Je commence tout juste à m'intéresser à Objective-C et Cocoa en vue de jouer avec le SDK de l'iPhone. Je suis relativement à l'aise avec le langage C. malloc y free mais le système de comptage des références de Cocoa me laisse plutôt perplexe. On m'a dit que c'était très élégant une fois qu'on l'avait compris, mais je n'ai pas encore passé le cap.

Comment release , retain y autorelease et quelles sont les conventions relatives à leur utilisation ?

(Ou à défaut, qu'avez-vous lu qui vous a aidé à le comprendre ?)

148voto

Matt Dillard Points 9040

Commençons par retain y release ; autorelease n'est en fait qu'un cas particulier une fois que vous avez compris les concepts de base.

En Cocoa, chaque objet garde la trace du nombre de fois qu'il est référencé (plus précisément, la balise NSObject La classe de base l'implémente). En appelant retain sur un objet, vous lui dites que vous voulez augmenter son nombre de références d'une unité. En appelant release vous dites à l'objet que vous le lâchez, et son compte de référence est décrémenté. Si, après avoir appelé release le nombre de références est maintenant égal à zéro, alors la mémoire de cet objet est libérée par le système.

La principale différence avec malloc y free est qu'un objet donné n'a pas à s'inquiéter du fait que d'autres parties du système se plantent parce que vous avez libéré la mémoire qu'elles utilisaient. En supposant que tout le monde joue le jeu et conserve/libère selon les règles, lorsqu'un morceau de code conserve puis libère l'objet, tout autre morceau de code faisant également référence à l'objet ne sera pas affecté.

Ce qui peut parfois prêter à confusion, c'est de savoir dans quelles circonstances vous devez appeler retain y release . Ma règle générale est que si je veux m'accrocher à un objet pendant un certain temps (s'il s'agit d'une variable membre d'une classe, par exemple), je dois m'assurer que le nombre de références de l'objet est au courant de mon existence. Comme décrit ci-dessus, le nombre de références d'un objet est incrémenté en appelant retain . Par convention, il est également incrémenté (fixé à 1, en fait) lorsque l'objet est créé avec une méthode "init". Dans l'un ou l'autre de ces cas, il est de ma responsabilité d'appeler la méthode release sur l'objet quand j'en ai fini avec lui. Si je ne le fais pas, il y aura une fuite de mémoire.

Exemple de création d'un objet :

NSString* s = [[NSString alloc] init];  // Ref count is 1
[s retain];                             // Ref count is 2 - silly
                                        //   to do this after init
[s release];                            // Ref count is back to 1
[s release];                            // Ref count is 0, object is freed

Maintenant pour autorelease . Autorelease est utilisé comme un moyen pratique (et parfois nécessaire) pour dire au système de libérer cet objet après un certain temps. Du point de vue de la plomberie, lorsque autorelease est appelé, la fonction NSAutoreleasePool est informé de l'appel. Le site NSAutoreleasePool sait maintenant qu'une fois qu'il a une opportunité (après l'itération actuelle de la boucle d'événement), il peut appeler release sur l'objet. De notre point de vue de programmeur, il s'occupe d'appeler release pour nous, afin que nous n'ayons pas à le faire (et en fait, nous ne devrions pas).

Ce qu'il est important de noter, c'est que (encore une fois, par convention) toute création d'objet classe retournent un objet autoreleased. Par exemple, dans l'exemple suivant, la variable "s" a un nombre de références de 1, mais après la fin de la boucle d'événement, elle sera détruite.

NSString* s = [NSString stringWithString:@"Hello World"];

Si vous voulez vous accrocher à cette chaîne, vous devez appeler retain explicitement, et ensuite explicitement release quand vous aurez terminé.

Considérez le bout de code suivant (très artificiel), et vous verrez une situation dans laquelle autorelease est nécessaire :

- (NSString*)createHelloWorldString
{
    NSString* s = [[NSString alloc] initWithString:@"Hello World"];

    // Now what?  We want to return s, but we've upped its reference count.
    // The caller shouldn't be responsible for releasing it, since we're the
    // ones that created it.  If we call release, however, the reference 
    // count will hit zero and bad memory will be returned to the caller.  
    // The answer is to call autorelease before returning the string.  By 
    // explicitly calling autorelease, we pass the responsibility for
    // releasing the string on to the thread's NSAutoreleasePool, which will
    // happen at some later time.  The consequence is that the returned string 
    // will still be valid for the caller of this function.
    return [s autorelease];
}

Je réalise que tout cela est un peu confus - à un moment donné, cependant, il y aura un déclic. Voici quelques références pour vous aider à démarrer :

  • Introduction d'Apple à la gestion de la mémoire.
  • Programmation Cocoa pour Mac OS X (4ème édition) par Aaron Hillegas - un livre très bien écrit avec beaucoup de bons exemples. Il se lit comme un tutoriel.
  • Si vous voulez vraiment plonger, vous pouvez vous rendre à Big Nerd Ranch . Il s'agit d'un centre de formation dirigé par Aaron Hillegas - l'auteur du livre mentionné ci-dessus. J'y ai suivi le cours Intro to Cocoa il y a plusieurs années, et c'était une excellente façon d'apprendre.

10voto

Andrew Grant Points 35305

Si vous comprenez le processus de conservation/libération, il existe deux règles d'or qui sont évidentes pour les programmeurs Cocoa expérimentés, mais qui sont malheureusement rarement expliquées aussi clairement aux nouveaux arrivants.

  1. Si une fonction qui renvoie un objet a alloc , create o copy dans son nom, alors l'objet est à vous. Vous devez appeler [object release] lorsque vous avez terminé. Ou CFRelease(object) s'il s'agit d'un objet Core-Foundation.

  2. S'il ne comporte PAS l'un de ces mots dans son nom, l'objet appartient à quelqu'un d'autre. Vous devez appeler [object retain] si vous souhaitez conserver l'objet après la fin de votre fonction.

Vous seriez bien inspiré de suivre également cette convention dans les fonctions que vous créez vous-même.

(Pique-assiette : Oui, il existe malheureusement quelques appels API qui font exception à ces règles, mais ils sont rares).

8voto

Chris Hanson Points 34485

Si vous écrivez du code pour le bureau et que vous pouvez cibler Mac OS X 10.5, vous devriez au moins envisager d'utiliser le garbage collection d'Objective-C. Il simplifiera vraiment la plupart de vos développements. Il simplifiera vraiment la plupart de vos développements - c'est pour cela qu'Apple a mis tous ses efforts à le créer en premier lieu, et à le rendre performant.

Quant aux règles de gestion de la mémoire lorsqu'on n'utilise pas la GC :

  • Si vous créez un nouvel objet en utilisant +alloc/+allocWithZone: , +new , -copy o -mutableCopy ou si vous -retain un objet, vous en prenez la propriété et devez vous assurer qu'il est envoyé -release .
  • Si vous recevez un objet d'une autre manière, vous êtes no le propriétaire de l'objet et devrait no veiller à ce qu'il soit envoyé -release .
  • Si vous voulez vous assurer qu'un objet est envoyé -release vous pouvez soit l'envoyer vous-même, soit envoyer l'objet -autorelease et le courant pool d'autolibération l'enverra -release (une fois par reçu -autorelease ) lorsque la piscine est vidée.

Typiquement -autorelease est utilisé comme un moyen de s'assurer que les objets vivent pendant la durée de l'événement en cours, mais qu'ils sont nettoyés ensuite, car il existe un pool d'autorelease qui entoure le traitement des événements de Cocoa. Dans Cocoa, c'est loin Il est plus fréquent de renvoyer à l'appelant des objets qui sont libérés automatiquement que de renvoyer des objets que l'appelant doit lui-même libérer.

6voto

NilObject Points 7874

Objective-C utilise Comptage de référence ce qui signifie que chaque objet a un nombre de références. Lorsqu'un objet est créé, il a un nombre de référence de "1". En d'autres termes, lorsqu'un objet est référencé (c'est-à-dire stocké quelque part), il est "retenu", ce qui signifie que son nombre de références est augmenté de un. Lorsqu'un objet n'est plus nécessaire, il est "libéré", ce qui signifie que son nombre de références est diminué de un.

Lorsque le nombre de références d'un objet est égal à 0, l'objet est libéré. C'est le comptage de référence de base.

Pour certains langages, les références sont automatiquement augmentées et diminuées, mais objective-c ne fait pas partie de ces langages. Le programmeur est donc responsable de la conservation et de la libération des références.

Une façon typique d'écrire une méthode est :

id myVar = [someObject someMessage];
.... do something ....;
[myVar release];
return someValue;

Le problème de devoir se rappeler de libérer toute ressource acquise à l'intérieur du code est à la fois fastidieux et source d'erreurs. Objective-C introduit un nouveau concept visant à rendre cette tâche beaucoup plus facile : Les Autorelease Pools. Les Autorelease Pools sont des objets spéciaux qui sont installés sur chaque thread. Il s'agit d'une classe assez simple, si vous cherchez NSAutoreleasePool.

Lorsqu'un objet reçoit un message "autorelease", il recherche tous les pools d'autorelease présents sur la pile pour le thread en cours. Il ajoutera l'objet à la liste des objets auxquels il enverra un message "release" à un moment donné dans le futur, généralement lorsque le pool lui-même sera libéré.

En prenant le code ci-dessus, vous pouvez le réécrire pour qu'il soit plus court et plus facile à lire en disant :

id myVar = [[someObject someMessage] autorelease];
... do something ...;
return someValue;

Puisque l'objet est autorelease, nous n'avons plus besoin d'appeler explicitement "release" sur lui. En effet, nous savons qu'un pool d'autorelease le fera pour nous plus tard.

J'espère que cela vous aidera. L'article de Wikipédia est assez bon sur le comptage des références. Plus d'informations sur Les pools d'autorelease peuvent être trouvés ici . Notez également que si vous construisez pour Mac OS X 10.5 et ultérieur, vous pouvez demander à Xcode de construire avec le ramassage des déchets activé, ce qui vous permet d'ignorer complètement les fonctions retain/release/autorelease.

6voto

Matt Sheppard Points 32256

Joshua (#6591) - Le système de collecte des déchets de Mac OS X 10.5 semble plutôt cool, mais n'est pas disponible pour l'iPhone (ou si vous voulez que votre application fonctionne sur des versions antérieures à 10.5 de Mac OS X).

En outre, si vous écrivez une bibliothèque ou quelque chose qui pourrait être réutilisé, l'utilisation du mode GC verrouille toute personne utilisant le code en utilisant également le mode GC, donc si je comprends bien, toute personne essayant d'écrire un code largement réutilisable a tendance à opter pour la gestion manuelle de la mémoire.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X