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

Je commence à regarder Objective-C et Cocoa en vue de jouer avec le SDK iPhone. Je suis assez à l’aise avec le concept malloc et free de C, mais le schéma de comptage des références de Cocoa me rend plutôt confus. On me dit que c’est très élégant une fois que vous l’aurez compris, mais je ne suis pas encore au bout de mes peines.

Comment release , retain et autorelease travail et quelles sont les conventions concernant leur utilisation?

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

Commençons par retain et release ; autorelease est vraiment un cas particulier une fois que vous avez compris les concepts de base.

Dans Cocoa, chaque object garde la trace du nombre de fois qu’il est référencé (en particulier, la classe de base NSObject implémente ceci). En appelant la retain sur un object, vous lui dites que vous souhaitez augmenter son compte de référence de un. En appelant release , vous indiquez à l’object que vous le lâchez et son compte de référence est décrémenté. Si, après avoir appelé release , le compte de référence est désormais zéro, la mémoire de cet object est libérée par le système.

Ce qui diffère fondamentalement de malloc et free c’est que tout object donné n’a pas besoin de s’inquiéter des autres parties du système qui se bloquent parce que vous avez libéré de la mémoire. En supposant que tout le monde joue et retient / libère selon les règles, quand un morceau de code retient puis libère l’object, tout autre morceau de code faisant également référence à l’object ne sera pas affecté.

Ce qui peut parfois être déroutant, c’est de connaître les circonstances dans lesquelles vous devriez appeler « retain et release . Ma règle générale est que si je veux conserver un object 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’object me concerne. Comme décrit ci-dessus, le décompte de références d’un object est incrémenté en appelant retain . Par convention, il est également incrémenté (défini sur 1, vraiment) lorsque l’object est créé avec une méthode “init”. Dans l’un ou l’autre de ces cas, il est de ma responsabilité d’appeler la release sur l’object lorsque j’en ai terminé. Si je ne le fais pas, il y aura une fuite de mémoire.

Exemple de création d’object:

 NSSsortingng* s = [[NSSsortingng 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 object après un certain temps. Du sharepoint vue de la plomberie, lorsque autorelease est appelé, NSAutoreleasePool du thread en NSAutoreleasePool est alerté de l’appel. NSAutoreleasePool sait maintenant qu’une fois qu’il a une opportunité (après l’itération en cours de la boucle d’événement), il peut appeler la release sur l’object. De notre sharepoint vue en tant que programmeurs, nous prenons en charge l’appel de la release pour nous, donc nous n’avons pas à le faire (et en fait, nous ne devrions pas).

Ce qui est important de noter est que (encore une fois, par convention) toutes les méthodes de classe de création d’objects renvoient un object auto-libéré. Par exemple, dans l’exemple suivant, la variable “s” a un compte de référence de 1, mais une fois la boucle d’événement terminée, elle sera détruite.

 NSSsortingng* s = [NSSsortingng ssortingngWithSsortingng:@"Hello World"]; 

Si vous voulez vous accrocher à cette chaîne, vous devez appeler explicitement keep, puis la release explicitement lorsque vous avez terminé.

Considérez le bit suivant (très artificiel), et vous verrez une situation où autorelease est requirejs:

 - (NSSsortingng*)createHelloWorldSsortingng { NSSsortingng* s = [[NSSsortingng alloc] initWithSsortingng:@"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 ssortingng. By // explicitly calling autorelease, we pass the responsibility for // releasing the ssortingng on to the thread's NSAutoreleasePool, which will // happen at some later time. The consequence is that the returned ssortingng // 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 pour Mac OS X (4ème édition) , par Aaron Hillegas – un livre très bien écrit avec de nombreux exemples. Il se lit comme un tutoriel.
  • Si vous plongez vraiment, vous pourriez vous diriger vers Big Nerd Ranch . Ceci est un centre de formation géré par Aaron Hillegas – l’auteur du livre mentionné ci-dessus. J’ai assisté au cours d’introduction au cacao il y a plusieurs années, et c’était une excellente façon d’apprendre.

Si vous comprenez le processus de rétention / libération, il y a deux règles d’or évidentes pour les programmeurs de cacao établis, mais elles sont malheureusement rarement expliquées clairement pour les nouveaux venus.

  1. Si une fonction qui renvoie un object a l’ alloc , create ou copy son nom, alors l’object est à vous. Vous devez appeler [object release] lorsque vous en avez fini avec. Ou CFRelease(object) , s’il s’agit d’un object Core-Foundation.

  2. S’il n’a pas un de ces mots dans son nom, l’object appartient à quelqu’un d’autre. Vous devez appeler [object retain] si vous souhaitez conserver l’object 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 sont des exceptions à ces règles mais ils sont rares).

Si vous écrivez du code pour le bureau et que vous pouvez cibler Mac OS X 10.5, vous devriez au moins utiliser le ramasse-miettes Objective-C. Cela simplifiera vraiment la majeure partie de votre développement. C’est pourquoi Apple a tout mis en œuvre pour le créer et le rendre performant.

En ce qui concerne les règles de gestion de la mémoire lorsque vous n’utilisez pas GC:

  • Si vous créez un nouvel object en utilisant +alloc/+allocWithZone: +new , -copy ou -mutableCopy ou si vous -retain un object, vous en prenez possession et vous devez vous assurer qu’il est envoyé -release .
  • Si vous recevez un object autrement, vous n’en êtes pas le propriétaire et vous ne -release pas vous assurer qu’il est envoyé.
  • Si vous voulez vous assurer qu’un object est envoyé -release vous pouvez soit l’envoyer vous-même, soit envoyer l’object -autorelease et le pool d’autorelease en cours l’enverra ( -release une fois par -autorelease reçu) lorsque le pool sera vidé.

Généralement, -autorelease est utilisé pour garantir que les objects sont -autorelease pendant la durée de l’événement en cours, mais ils sont nettoyés par la suite, car il existe un pool d’autorelease qui entoure le traitement des événements de Cocoa. Dans Cocoa, il est beaucoup plus courant de renvoyer des objects à un appelant qui sont libérés automatiquement que de renvoyer des objects que l’appelant lui-même doit libérer.

Objective-C utilise le comptage de références , ce qui signifie que chaque object a un compte de référence. Lorsqu’un object est créé, il a un nombre de références de “1”. En termes simples, lorsqu’un object est référencé (c’est-à-dire stocké quelque part), il est “conservé”, ce qui signifie que son compte de référence est augmenté de un. Lorsqu’un object n’est plus nécessaire, il est “libéré”, ce qui signifie que son compte de référence est diminué de un.

Lorsque le compte de référence d’un object est 0, l’object est libéré. C’est le 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 façon typique d’écrire une méthode est la suivante:

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

Le problème de devoir se souvenir de libérer les ressources acquises à l’intérieur du code est à la fois fastidieux et sujet aux erreurs. Objective-C introduit un autre concept visant à faciliter les choses: les pools Autorelease. Les pools Autorelease sont des objects spéciaux installés sur chaque thread. Ils sont une classe assez simple, si vous recherchez NSAutoreleasePool.

Lorsqu’un object reçoit un message “autorelease”, l’object recherchera tous les pools d’autorelease situés sur la stack pour ce thread en cours. Il appenda l’object à la liste en tant qu’object pour envoyer un message “release” à un moment donné, 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; 

Parce que l’object est automatiquement relâché, il n’est plus nécessaire d’appeler explicitement “release” sur celui-ci. C’est parce que nous soaps qu’un pool d’autorelease le fera plus tard.

Espérons que cela aide. L’article de Wikipedia est plutôt bon sur le comptage de références. Vous trouverez plus d’informations sur les pools d’autorelease ici . Notez également que si vous construisez pour Mac OS X 10.5 et versions ultérieures, vous pouvez demander à Xcode de générer la récupération de place activée, ce qui vous permet d’ignorer complètement la rétention / publication / autorelease.

Joshua (# 6591) – Les opérations de nettoyage de la mémoire dans Mac OS X 10.5 semblent plutôt intéressantes, mais ne sont pas disponibles pour l’iPhone (ou si vous souhaitez 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é, le mode GC verrouille toute personne utilisant le code en utilisant également le mode GC. Si je comprends bien, quiconque essaie d’écrire du code largement réutilisable a tendance à le gérer. mémoire manuellement.

Comme toujours, lorsque les gens commencent à essayer de reformuler le matériel de référence, ils obtiennent presque toujours quelque chose de mal 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 il existe un résumé bref mais précis des règles de gestion de la mémoire .

Je ne vais pas append à la spécificité de Retain / Release autre que vous pourriez penser à perdre 50 $ et obtenir le livre Hillegass, mais je suggère fortement de commencer à 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 les outils de performance. Je commencerais par Leaks, qui est l’un des nombreux instruments disponibles, mais qui vous aidera à savoir quand vous avez oublié de sortir. Il est très difficile de savoir avec quelle quantité d’informations vous serez présenté. Mais consultez ce tutoriel pour vous lever et avancer rapidement:
TUTORIEL DE CACAO: FIXATION DE FUITES DE MÉMOIRE AVEC DES INSTRUMENTS

En fait, essayer de forcer les fuites pourrait être une meilleure façon d’apprendre à les prévenir! Bonne chance 😉

Matt Dillard a écrit :

retour [[autorelease] libération];

Autorelease ne conserve pas l’object. Autorelease le met simplement en queue pour être publié plus tard. Vous ne voulez pas avoir de déclaration de sortie.

Ma collection habituelle d’articles de gestion de la mémoire Cocoa:

gestion de la mémoire de cacao

Un screencast gratuit est disponible sur le réseau iDeveloperTV

Gestion de la mémoire en Objective-C

La réponse de NilObject est un bon début. Voici quelques informations supplémentaires concernant la gestion manuelle de la mémoire ( requirejse sur l’iPhone ).

Si vous alloc/init personnellement alloc/init un object, celui-ci contient un compte de référence de 1. Vous êtes responsable du nettoyage après qu’il ne soit plus nécessaire, soit en appelant [foo release] ou [foo autorelease] . release le nettoie tout de suite, alors qu’autorelease ajoute l’object au pool autorelease, qui le libérera automatiquement ultérieurement.

autorelease est principalement utilisé lorsque vous avez une méthode qui doit renvoyer l’object en question ( vous ne pouvez donc pas le libérer manuellement, sinon vous retournerez un object nul ) mais vous ne voulez pas le conserver, que ce soit .

Si vous acquérez un object où vous n’avez pas appelé alloc / init pour l’obtenir – par exemple:

 foo = [NSSsortingng ssortingngWithSsortingng:@"hello"]; 

mais vous voulez vous accrocher à cet object, vous devez appeler [foo retain]. Sinon, il est possible qu’il soit autoreleased et que vous autoreleased une référence nulle (comme dans l’exemple ssortingngWithSsortingng ci-dessus ). Lorsque vous n’en avez plus besoin, appelez [foo release] .

Les réponses ci-dessus reprennent clairement ce que dit la documentation; le problème que rencontrent le plus les nouvelles personnes concerne les cas non documentés. Par exemple:

  • Autorelease : les docs disent que cela déclenchera une sortie “dans le futur”. QUAND?! En gros, vous pouvez compter sur l’object jusqu’à ce que vous quittiez votre code dans la boucle d’événements du système. Le système PEUT libérer l’object à tout moment après le cycle d’événement en cours. (Je pense que Matt a dit ça plus tôt.)

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

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

     NSSsortingng *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 sont déconcertés est de ne pas comprendre quelles routines renvoient un object 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 compte de rétention

  • Dans un bloc donné, l’utilisation de -copy, -alloc et -retain doit être équivalente à l’utilisation de -release et -autorelease.
  • Les objects créés à l’aide de constructeurs de commodité (par exemple, ssortingngWithSsortingng de NSSsortingng) sont considérés comme auto-libérés.
  • Implémentez une méthode -dealloc pour libérer les variables d’instance que vous possédez

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

La seconde puce dit: si vous utilisez un constructeur de commodité et que vous avez besoin de l’object pour traîner (comme avec une image à dessiner ultérieurement), vous devez le conserver (puis le relâcher ultérieurement).

Le 3ème devrait être explicite.

Beaucoup de bonnes informations sur cocoadev aussi:

  • Gestion de la mémoire
  • Règles de base

Comme plusieurs personnes l’ont déjà mentionné, l’ introduction de la gestion de la mémoire d’ Apple est de loin le meilleur endroit pour commencer.

Un lien utile que je n’ai pas encore vu est la gestion de mémoire pratique . Vous le trouverez au milieu des documents d’Apple si vous les lisez, mais cela vaut la peine d’être lié directement. C’est un excellent résumé des règles de gestion de la mémoire avec des exemples et des erreurs courantes (en gros, ce que d’autres réponses tentent d’expliquer ici, mais pas aussi).