Comment fonctionne le pool d'autelease NSAutoreleasePool?

95

Si je comprends bien, tout ce qui est créé avec un alloc , un nouveau ou une copie doit être publié manuellement. Par exemple:

int main(void) {
    NSString *string;
    string = [[NSString alloc] init];
    /* use the string */
    [string release];
}

Ma question, cependant, est-ce que ce ne serait pas tout aussi valable?:

int main(void) {
    NSAutoreleasePool *pool;
    pool = [[NSAutoreleasePool alloc] init];
    NSString *string;
    string = [[[NSString alloc] init] autorelease];
    /* use the string */
    [pool drain];
}
James Sumners
la source

Réponses:

68

Oui, votre deuxième extrait de code est parfaitement valide.

Chaque fois que -autorelease est envoyé à un objet, il est ajouté au pool de diffusion automatique le plus interne. Lorsque la piscine est vidangée, il envoie simplement -release à tous les objets de la piscine.

Les pools de libération automatique sont simplement une commodité qui vous permet de reporter l'envoi -release à "plus tard". Ce «plus tard» peut se produire à plusieurs endroits, mais le plus courant dans les applications d'interface graphique Cocoa se situe à la fin du cycle de boucle d'exécution actuel.

Kperryua
la source
5
où est la fin du cycle actuel de la boucle d'exécution, si je n'ai pas de boucle?
Merci
24
«Le plus extérieur» ne devrait-il pas être «le plus intérieur»?
Mike Weller
an objectdevrait être an object that is a subclass of NSObject or NSProxy and doesn't override -autorelease.
1
EDIT: Changé le plus extérieur en le plus intérieur.
chakrit
1
Important: si vous utilisez le comptage automatique des références (ARC), vous ne pouvez pas utiliser directement les pools de libération automatique. À la place, vous utilisez des blocs @autoreleasepool. De developer.apple.com/library/mac/#documentation/Cocoa/Reference/…
Md Mahbubur Rahman
37

NSAutoreleasePool: drain vs release

Puisque la fonction de drainet releasesemble prêter à confusion, il peut être utile de clarifier ici (bien que cela soit couvert dans la documentation ...).

À proprement parler, du point de vue général drainn'est pas équivalent à release:

Dans un environnement compté par référence, draineffectue les mêmes opérations que release, donc les deux sont en ce sens équivalents. Pour souligner, cela signifie que vous ne fuyez pas une piscine si vous utilisez drainplutôt que release.

Dans un environnement garbage collection, releaseest un no-op. Cela n'a donc aucun effet. drain, d'autre part, contient un indice pour le collectionneur qu'il devrait "collecter si nécessaire". Ainsi, dans un environnement garbage collection, l'utilisation drainaide le système à équilibrer les balayages de collecte.

mmalc
la source
4
Il est fondamentalement impossible de «fuir» un NSAutoreleasePool. En effet, les pools fonctionnent comme une pile. L'instanciation d'un pool pousse ce pool au sommet de cette pile de pool de libération automatique de threads. -releasefait sortir ce pool de la pile ET de tous les pools qui ont été poussés dessus, mais pour une raison quelconque, n'ont pas été sautés.
johne
7
En quoi est-ce pertinent par rapport à ce que j'ai écrit?
mmalc
2
J'aime la façon dont il a pris le temps d'audacieux ET. CASSER!
Billy Gray
17

Comme déjà souligné, votre deuxième extrait de code est correct.

Je voudrais suggérer une manière plus succincte d'utiliser le pool de libération automatique qui fonctionne sur tous les environnements (comptage de ref, GC, ARC) et évite également la confusion drain / libération:

int main(void) {
  @autoreleasepool {
    NSString *string;
    string = [[[NSString alloc] init] autorelease];
    /* use the string */
  }
}

Dans l'exemple ci-dessus, veuillez noter le bloc @autoreleasepool . Ceci est documenté ici .

Néovibrant
la source
2
Veuillez noter que la libération automatique n'est pas autorisée avec ARC.
dmirkitanov
1
Pour clarifier, il faut utiliser le @autoreleasepoolbloc avec ARC.
Simon
7

Non vous vous trompez. La documentation indique clairement que sous non-GC, -drain équivaut à -release, ce qui signifie que le NSAutoreleasePool ne sera pas divulgué.

Kperryua
la source
Je me suis demandé pourquoi Xcode générerait du code avec -drain si tel était le cas. J'ai utilisé -drain parce que je pensais que c'était équivalent à -release basé sur le code généré par Xcode.
James Sumners
1
Il est fondamentalement impossible de `` fuir '' un NSAutoreleasePool: developer.apple.com/mac/library/documentation/Cocoa/Conceptual/…
johne
0

l'envoi de la libération automatique au lieu de la libération à un objet prolonge la durée de vie de cet objet au moins jusqu'à ce que le pool lui-même soit vidé (cela peut être plus long si l'objet est conservé par la suite). Un objet peut être placé plusieurs fois dans le même pool, auquel cas il reçoit un message de libération à chaque fois qu'il a été placé dans le pool.

Hardik Mamtora
la source
-2

Oui et non. Vous finiriez par libérer la mémoire de chaîne mais «fuir» l'objet NSAutoreleasePool dans la mémoire en utilisant drain au lieu de release si vous l'exécutiez dans un environnement garbage collection (non géré par la mémoire). Cette "fuite" rend simplement l'instance de NSAutoreleasePool "inaccessible" comme tout autre objet sans pointeurs puissants sous GC, et l'objet sera nettoyé lors de la prochaine exécution de GC, ce qui pourrait très bien être directement après l'appel à -drain:

drainer

Dans un environnement garbage collection, déclenche garbage collection si la mémoire allouée depuis la dernière collecte est supérieure au seuil actuel; autrement se comporte comme une libération. ... Dans un environnement garbage collection, cette méthode appelle finalement objc_collect_if_needed.

Sinon, c'est similaire à la façon dont -releasese comporte sous non-GC, oui. Comme d'autres l'ont indiqué, il -releasen'y a pas d'opération sous GC, donc le seul moyen de s'assurer que le pool fonctionne correctement sous GC est à travers -drain, et -drainsous non-GC fonctionne exactement comme -releasesous non-GC, et communique sans doute ses fonctionnalités plus clairement comme bien.

Je dois souligner que votre déclaration "tout ce qui est appelé avec new, alloc ou init" ne doit pas inclure "init" (mais doit inclure "copy"), car "init" n'alloue pas de mémoire, il configure uniquement l'objet (constructeur mode). Si vous avez reçu un objet alloué et que votre fonction n'a appelé init que comme tel, vous ne le libérez pas:

- (void)func:(NSObject*)allocd_but_not_init
{
    [allocd_but_not_init init];
}

Cela ne consomme pas plus de mémoire que celle avec laquelle vous avez déjà commencé (en supposant que init n'instancie pas les objets, mais vous n'êtes pas responsable de ceux-ci de toute façon).

Loren Segal
la source
Je ne me sens pas à l'aise de laisser cette réponse comme acceptée lorsque vos informations sur le drain ne sont pas tout à fait exactes. Voir developer.apple.com/documentation/Cocoa/Reference/Foundation/... Update et je réaccepterai.
James Sumners
Qu'est-ce qui est inexact dans la réponse? Dans un environnement garbage collection (comme indiqué), drain ne supprime pas l'AutoReleasePool, vous ferez donc une fuite de mémoire à moins que vous n'utilisiez release. La citation que j'ai énumérée était directement de la bouche du cheval, les documents sur le drain.
Loren Segal
1
Loren: Sous GC, - [NSAutoreleasePool drain] déclenchera une collecte. -retain, -release et -autorelease sont tous ignorés par le collecteur; c'est pourquoi -drain est utilisé sur les pools de libération automatique sous GC.
Chris Hanson
Dans la documentation de 'drain': Dans un environnement de mémoire gérée, cela se comporte comme l'appel de release. Ainsi, vous ne perdrez pas de mémoire si vous utilisez «drain» au lieu de release.
mmalc
-[NSAutoreleasePool release]dans un environnement garbage collection est un no-op. -[NSAutoreleasePool drain]fonctionne à la fois dans les environnements de référence et de récupération de place.
Jonathan Sterling