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

122

Je commence tout juste à jeter un œil à Objective-C et Cocoa en vue de jouer avec le SDK iPhone. Je suis raisonnablement à l'aise avec les C mallocet le freeconcept, mais le schéma de comptage des références de Cocoa m'a plutôt confus. On me dit que c'est très élégant une fois que vous l'avez compris, mais je ne suis tout simplement pas encore sur la bosse.

Comment release, retainet le autoreleasetravail et quelles sont les conventions sur leur utilisation?

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

Matt Sheppard
la source

Réponses:

148

Commençons par retainet release; autoreleaseest vraiment juste un cas spécial une fois que vous comprenez les concepts de base.

Dans Cocoa, chaque objet garde une trace du nombre de fois où il est référencé (en particulier, la NSObjectclasse de base l'implémente). En appelant retainun objet, vous lui dites que vous souhaitez augmenter son nombre de références de un. En appelant release, vous dites à l'objet que vous le lâchez, et son nombre de références est décrémenté. Si, après l'appel release, le nombre de références est maintenant nul, alors la mémoire de cet objet est libérée par le système.

La façon fondamentale dont cela diffère mallocet freeest que tout objet donné n'a pas besoin de s'inquiéter de la panne d'autres parties du système parce que vous avez libéré la mémoire qu'ils utilisaient. En supposant que tout le monde joue et retient / libère selon les règles, lorsqu'un morceau de code conserve puis libère l'objet, tout autre morceau de code référençant également l'objet ne sera pas affecté.

Ce qui peut parfois prêter à confusion, c'est de connaître les circonstances dans lesquelles vous devez appeler retainet 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 dans une classe, par exemple), je dois m'assurer que le nombre de références de l'objet me connaît. Comme décrit ci-dessus, le nombre de références d'un objet est incrémenté lors de l'appel retain. Par convention, il est également incrémenté (mis à 1, vraiment) lorsque l'objet est créé avec une méthode "init". Dans l'un ou l'autre de ces cas, il est de ma responsabilité de faire appel releaseà l'objet lorsque j'en ai fini avec lui. Sinon, il y aura une fuite de mémoire.

Exemple de création d'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) de dire au système de libérer cet objet après un certain temps. Du point de vue de la plomberie, lors de l' autoreleaseappel, le thread actuel NSAutoreleasePoolest alerté de l'appel. Le NSAutoreleasePoolsait maintenant qu'une fois qu'il obtient une opportunité (après l'itération courante de la boucle d'événement), il peut appeler releasel'objet. De notre point de vue en tant que programmeurs, il prend soin de releasenous appeler , donc nous n'avons pas à le faire (et en fait, nous ne devrions pas).

Ce qui est important à noter, c'est que (encore une fois, par convention) toutes les méthodes de classe de création d'objet renvoient un objet publié automatiquement. Par exemple, dans l'exemple suivant, la variable «s» a un nombre de références de 1, mais une fois la boucle d'événements terminée, elle sera détruite.

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

Si vous voulez vous accrocher à cette chaîne, vous devez l'appeler retainexplicitement, puis explicitement releaselorsque vous avez terminé.

Considérez le morceau de code suivant (très artificiel), et vous verrez une situation où autoreleaseest 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 me rends compte que tout cela est un peu déroutant - à un moment donné, cependant, il cliquera. Voici quelques références pour vous lancer:

  • Introduction d' Apple à la gestion de la mémoire.
  • Cocoa Programming for Mac OS X (4e édition) , par Aaron Hillegas - un livre très bien écrit avec beaucoup de bons exemples. Cela se lit comme un tutoriel.
  • Si vous plongez vraiment, vous pouvez vous rendre au Big Nerd Ranch . Il s'agit d'un centre de formation géré par Aaron Hillegas - l'auteur du livre mentionné ci-dessus. J'ai suivi le cours d'introduction au cacao il y a plusieurs années, et c'était une excellente façon d'apprendre.
Matt Dillard
la source
8
Vous avez écrit: "En appelant autorelease, nous augmentons temporairement le nombre de références". Je pense que cela est faux; autorelease marque uniquement l'objet à publier dans le futur, cela n'augmente pas le nombre de ref: cocoadev.com/index.pl?AutoRelease
LKM
2
"Maintenant pour la libération automatique. Autorelease est utilisé comme un moyen pratique (et parfois nécessaire) de dire au système de libérer cet objet après un certain temps." En guise de phrase d'introduction, c'est faux. Il ne dit pas au système de "libérer [it] up", il lui dit de décrémenter le nombre de retenues.
mmalc
3
Merci beaucoup pour la bonne explication. Juste une chose qui n'est toujours pas claire. Si NSString* s = [[NSString alloc] initWithString:@"Hello World"];renvoie un objet libéré automatiquement (tel que vous l'écrivez), pourquoi dois-je faire un return [s autorelease];et le définir à nouveau sur "autorelease" et pas seulement return s?
znq
3
@Stefan: [[NSString alloc] initWithString:@"Hello World"]ne retournera PAS d'objet publié automatiquement. Chaque fois qu'il allocest appelé, le nombre de références est défini sur 1 et il est de la responsabilité de ce code de s'assurer qu'il est libéré. L' [NSString stringWithString:]appel, d'autre part, ne retourne un objet autoreleased.
Matt Dillard
6
Anecdotes amusantes: puisque la réponse utilise @ "" et NSString, les chaînes sont constantes partout et, par conséquent, le nombre absolu de rétention sera à la fois constant et totalement hors de propos .... ne rend pas la réponse fausse, en aucun cas, juste renforce le fait que les comptes de rétention absolus ne sont jamais vraiment quelque chose dont vous devriez vous inquiéter.
bbum
10

Si vous comprenez le processus de conservation / libération, alors il y a deux règles d'or qui sont "duh" évidentes pour les programmeurs Cocoa établis, mais qui sont malheureusement rarement énoncées clairement pour les nouveaux venus.

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

  2. S'il n'a PAS un de ces mots dans son nom, alors 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 servi de suivre également cette convention dans les fonctions que vous créez vous-même.

(Nitpickers: Oui, il y a malheureusement quelques appels d'API qui font exception à ces règles mais ils sont rares).

Andrew Grant
la source
11
Ceci est incomplet et inexact. Je continue de ne pas comprendre pourquoi les gens essaient de répéter les règles plutôt que de simplement pointer vers la documentation pertinente: developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgmt/…
mmalc
4
Les règles de la Core Foundation en particulier sont différentes de celles de Cocoa; voir developer.apple.com/documentation/CoreFoundation/Conceptual/…
mmalc
1
Je suis également en désaccord. Si une fonction renvoie quelque chose qu'elle ne veut pas posséder, elle doit le libérer automatiquement. C'est à l'appelant du job de fonctions de le conserver (si désiré). Il ne devrait rien avoir à voir avec le nom d'une méthode invoquée. C'est plus un codage de style C où la propriété des objets n'est pas claire.
Sam
1
Désolé! Je pense que j'ai été hâtif de voter à la baisse. Règles de gestion de la mémoire Votre réponse cite presque le document Apple.
Sam
8

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 Objective-C. Cela simplifiera vraiment la plupart de votre développement - c'est pourquoi Apple a tout mis en œuvre pour le créer en premier lieu et le faire fonctionner correctement.

Quant aux règles de gestion de la mémoire lorsque vous n'utilisez pas GC:

  • Si vous créez un nouvel objet en utilisant +alloc/+allocWithZone:, +new, -copyou -mutableCopyou si vous -retainun objet, vous prenez la propriété de celui - ci et doit veiller à ce qu'il soit envoyé -release.
  • Si vous recevez un objet d'une autre façon, vous n'êtes pas le propriétaire de celui - ci et devrait ne pas assurer qu'il est envoyé -release.
  • Si vous voulez vous assurer qu'un objet est envoyé, -releasevous pouvez l'envoyer vous-même, ou vous pouvez envoyer l'objet -autoreleaseet le pool de libération automatique actuel l'enverra -release(une fois par réception -autorelease) lorsque le pool est vidé.

Il -autoreleaseest généralement utilisé pour garantir que les objets vivent pendant toute la durée de l'événement en cours, mais sont nettoyés par la suite, car il existe un pool de libération automatique qui entoure le traitement des événements de Cocoa. Dans Cocoa, il est beaucoup plus courant de renvoyer des objets à un appelant qui sont libérés automatiquement que de renvoyer des objets que l'appelant lui-même doit libérer.

Chris Hanson
la source
6

Objective-C utilise le comptage de références , 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érences de "1". En termes simples, 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é. Il s'agit du comptage de référence de base.

Pour certaines langues, les références sont automatiquement augmentées et diminuées, mais objective-c ne fait pas partie de ces langues. Ainsi, le programmeur est responsable de la conservation et de la libération.

Une manière typique d'écrire une méthode est:

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

Le problème de devoir se souvenir de libérer toutes les ressources acquises à l'intérieur du code est à la fois fastidieux et sujet aux erreurs. Objective-C introduit un autre concept visant à rendre cela beaucoup plus facile: les pools de lancement automatique. Les pools de libération automatique sont des objets spéciaux installés sur chaque thread. Il s'agit d'une classe assez simple, si vous recherchez NSAutoreleasePool.

Lorsqu'un objet reçoit un message "autorelease" qui lui est envoyé, l'objet recherchera tous les pools de libération automatique se trouvant sur la pile pour ce thread actuel. Il ajoutera l'objet à la liste en tant qu'objet auquel envoyer un message de «libération» à un moment donné dans le futur, ce qui se produit généralement lorsque le pool lui-même est 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;

Étant donné que l'objet est libéré automatiquement, nous n'avons plus besoin d'appeler explicitement "release" dessus. C'est parce que nous savons que certains pools de libération automatique le feront pour nous plus tard.

Espérons que cela aide. L'article de Wikipedia est assez bon sur le comptage des références. Pour plus d'informations sur les pools de libération automatique, cliquez ici . Notez également que si vous construisez pour Mac OS X 10.5 et versions ultérieures, vous pouvez demander à Xcode de créer avec le ramasse-miettes activé, ce qui vous permet d'ignorer complètement la conservation / la libération / la libération automatique.

NilObject
la source
2
C'est tout simplement faux. Il n'est pas nécessaire d'envoyer une version ou une autorlease d'Object dans l'un ou l'autre des exemples présentés.
mmalc
6

Joshua (# 6591) - Le contenu de la collecte des ordures dans 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 les versions antérieures à 10.5 de Mac OS X).

De plus, si vous écrivez une bibliothèque ou quelque chose qui pourrait être réutilisé, l'utilisation du mode GC empêche toute personne utilisant le code d'utiliser également le mode GC, donc si je comprends bien, toute personne essayant d'écrire du code largement réutilisable a tendance à opter pour la gestion mémoire manuellement.

Matt Sheppard
la source
2
Il est parfaitement possible d'écrire un framework hybride prenant en charge à la fois le GC et le comptage de références.
mmalc
6

Comme toujours, lorsque les gens commencent à essayer de reformuler le matériel de référence, ils se trompent presque invariablement ou fournissent une description incomplète.

Apple fournit une description complète du système de gestion de la mémoire de Cocoa dans le Guide de programmation de la gestion de la mémoire pour Cocoa , à la fin duquel se trouve un résumé bref mais précis des règles de gestion de la mémoire .

mmalc
la source
1
Et pour les règles de résumé: developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgmt/…
Michael Baltaks
2
En fait, c'est un bien meilleur résumé d'une seule page: developer.apple.com/mac/library/documentation/Cocoa/Conceptual/…
Brian Moeskau
6

Je n'ajouterai pas à la spécificité de retenir / publier autre que vous voudrez peut-être penser à perdre 50 $ et à obtenir le livre Hillegass, mais je suggère fortement d'utiliser les outils Instruments très tôt dans le développement de votre application (même votre Premier!). Pour ce faire, exécutez-> Commencez avec des outils de performance. Je commencerais par Leaks qui n'est qu'un des nombreux instruments disponibles mais qui vous aidera à vous montrer quand vous avez oublié de sortir. La quantité d'informations qui vous sera présentée est décourageante. Mais consultez ce tutoriel pour vous lever et aller vite:
TUTORIEL CACAO: RÉPARER LES FUITES DE MÉMOIRE AVEC DES INSTRUMENTS

En fait, essayer de forcer les fuites pourrait être un meilleur moyen, à son tour, d'apprendre à les éviter! Bonne chance ;)

Rob
la source
5

Matt Dillard a écrit :

return [[s autorelease] release];

Autorelease ne conserve pas l'objet. Autorelease le met simplement en file d'attente pour être publié plus tard. Vous ne voulez pas y avoir de déclaration de sortie.

NilObject
la source
4

Un screencast gratuit est disponible sur le réseau iDeveloperTV

Gestion de la mémoire dans Objective-C

Abizern
la source
1
Malheureusement, ce lien est maintenant un 404.
Ari Braginsky
4

La réponse de NilObject est un bon début. Voici quelques informations supplémentaires relatives à la gestion manuelle de la mémoire ( obligatoire sur l'iPhone ).

Si vous personnellement alloc/initun objet, il est livré avec un nombre de références de 1. Vous êtes responsable du nettoyage après lui quand il n'est plus nécessaire, soit en appelant [foo release]ou [foo autorelease]. release le nettoie tout de suite, tandis que l'autorelease ajoute l'objet au pool d'autelease, qui le libèrera automatiquement ultérieurement.

autorelease est principalement pour lorsque vous avez une méthode qui a besoin de retourner l'objet en question ( vous ne pouvez donc pas le libérer manuellement, sinon vous retournerez un objet nul ) mais que vous ne voulez pas non plus le garder .

Si vous acquérez un objet où vous n'avez pas appelé alloc / init pour l'obtenir - par exemple:

foo = [NSString stringWithString:@"hello"];

mais vous voulez vous accrocher à cet objet, vous devez appeler [foo retenir]. Sinon, il est possible que cela obtienne autoreleasedet que vous vous accrochiez à une référence nulle (comme dans l' stringWithStringexemple ci-dessus ). Lorsque vous n'en avez plus besoin, appelez [foo release].

Mike McMaster
la source
2

Les réponses ci-dessus reprennent clairement ce que dit la documentation; le problème que rencontrent la plupart des nouvelles personnes, ce sont les cas sans papiers. Par exemple:

  • Autorelease : les documents indiquent qu'il déclenchera une version "à un moment donné dans le futur". QUAND?! Fondamentalement, vous pouvez compter sur l'objet jusqu'à ce que vous quittiez votre code dans la boucle d'événements système. Le système PEUT libérer l'objet à tout moment après le cycle d'événements en cours. (Je pense que Matt l'a dit plus tôt.)

  • Chaînes statiques : NSString *foo = @"bar";- devez-vous conserver ou libérer cela? Non, pourquoi pas

    -(void)getBar {
        return @"bar";
    }

    ...

    NSString *foo = [self getBar]; // still no need to retain or release
  • La règle de création : si vous l'avez créée, vous en êtes propriétaire et vous êtes censé la publier.

En général, la façon dont les nouveaux programmeurs Cocoa se trompent est de ne pas comprendre quelles routines renvoient un objet avec un retainCount > 0.

Voici un extrait de Règles très simples pour la gestion de la mémoire dans Cocoa :

Règles de comptage de rétention

  • Dans un bloc donné, l'utilisation de -copy, -alloc et -retain doit être égale à l'utilisation de -release et -autorelease.
  • Les objets créés à l'aide de constructeurs pratiques (par exemple, stringWithString de NSString) sont considérés comme libérés automatiquement.
  • Implémentez une méthode -dealloc pour libérer les variables d'instance que vous possédez

La première puce dit: si vous avez appelé alloc(ou new fooCopy), vous devez appeler la libération sur cet objet.

La deuxième puce dit: si vous utilisez un constructeur pratique et que vous avez besoin que l'objet traîne (comme pour une image à dessiner plus tard), vous devez le conserver (puis le relâcher plus tard).

Le 3ème devrait être explicite.

Olie
la source
"Autorelease: les documents indiquent qu'il déclenchera une version" à un moment donné dans le futur. "QUAND?!" La documentation est claire sur ce point: "autorelease signifie simplement" envoyer un message de publication plus tard "(pour une définition de plus tard - voir" Autorelease Pools ")." Exactement quand dépend de la pile du pool de libération automatique ...
mmalc
... "Le système PEUT libérer l'objet à tout moment après le cycle d'événements en cours." Cela rend le son du système plutôt moins déterministe qu'il ne l'est ...
mmalc
... NSString foo = [self getBar]; // toujours pas besoin de conserver ou de libérer Ceci est faux. Quiconque invoque getBar ne connaît pas les détails de l'implémentation, donc * devrait conserver / publier (généralement via des accesseurs) s'ils veulent l'utiliser en dehors de la portée actuelle.
mmalc
L'article «Règles très simples pour la gestion de la mémoire dans Cocoa» est à plusieurs égards obsolète - en particulier «Les objets créés à l'aide de constructeurs pratiques (par exemple, stringWithString de NSString) sont considérés comme autoreleased. n'est pas juste - il est simplement "n'appartenant pas au destinataire".
mmalc
0

Comme plusieurs personnes l'ont déjà mentionné, l' Introduction à la gestion de la mémoire d' Apple est de loin le meilleur point de départ.

Un lien utile que je n'ai pas encore vu mentionné est la gestion pratique de la mémoire . Vous le trouverez au milieu des documents d'Apple si vous les lisez, mais cela vaut la peine de créer un lien direct. C'est un brillant résumé des règles de gestion de la mémoire avec des exemples et des erreurs courantes (essentiellement ce que les autres réponses tentent d'expliquer ici, mais pas aussi bien).

Brian Moeskau
la source