Pourquoi @autoreleasepool est-il toujours nécessaire avec ARC?

193

Pour la plupart avec ARC (Automatic Reference Counting), nous n'avons pas du tout besoin de penser à la gestion de la mémoire avec les objets Objective-C. Il n'est plus permis de créer des NSAutoreleasePools, mais il existe une nouvelle syntaxe:

@autoreleasepool {
    …
}

Ma question est la suivante: pourquoi aurais-je besoin de cela alors que je ne suis pas censé libérer / relâcher automatiquement?


EDIT: Pour résumer succinctement ce que j'ai tiré de toutes les réponses et commentaires:

Nouvelle syntaxe:

@autoreleasepool { … } est une nouvelle syntaxe pour

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
…
[pool drain];

Plus important:

  • ARC utilise autoreleaseainsi que release.
  • Il a besoin d'un pool de libération automatique en place pour ce faire.
  • ARC ne crée pas le pool de libération automatique pour vous. Pourtant:
    • Le thread principal de chaque application Cocoa contient déjà un pool de libération automatique.
  • Il y a deux occasions où vous voudrez peut-être utiliser @autoreleasepool:
    1. Lorsque vous êtes dans un thread secondaire et qu'il n'y a pas de pool de libération automatique, vous devez créer le vôtre pour éviter les fuites, telles que myRunLoop(…) { @autoreleasepool { … } return success; }.
    2. Lorsque vous souhaitez créer un pool plus local, comme @mattjgalloway l'a montré dans sa réponse.
mk12
la source
1
Il y a aussi une troisième occasion: lorsque vous développez quelque chose qui n'est pas lié à UIKit ou NSFoundation. Quelque chose qui utilise des outils de ligne de commande ou plus
Garnik

Réponses:

219

ARC ne supprime pas les retenues, les versions et les versions automatiques, il ajoute simplement les éléments requis pour vous. Il y a donc encore des appels à retenir, il y a encore des appels à libérer, il y a toujours des appels à autorelease et il y a encore des pools de libération automatique.

Une des autres modifications qu'ils ont apportées avec le nouveau compilateur Clang 3.0 et ARC est qu'ils ont été remplacés NSAutoReleasePoolpar la @autoreleasepooldirective du compilateur. NSAutoReleasePoolétait toujours un peu un "objet" spécial de toute façon et ils ont fait en sorte que la syntaxe de son utilisation ne soit pas confondue avec un objet afin que ce soit généralement un peu plus simple.

Donc, fondamentalement, vous avez besoin @autoreleasepoolparce qu'il y a encore des pools de libération automatique à s'inquiéter. Vous n'avez tout simplement pas besoin de vous soucier de l'ajout d' autoreleaseappels.

Un exemple d'utilisation d'un pool de libération automatique:

- (void)useALoadOfNumbers {
    for (int j = 0; j < 10000; ++j) {
        @autoreleasepool {
            for (int i = 0; i < 10000; ++i) {
                NSNumber *number = [NSNumber numberWithInt:(i+j)];
                NSLog(@"number = %p", number);
            }
        }
    }
}

Un exemple extrêmement artificiel, bien sûr, mais si vous n'aviez pas l' @autoreleasepoolintérieur de la forboucle externe, vous libéreriez 100000000 objets plus tard au lieu de 10000 à chaque fois autour de la forboucle externe .

Mise à jour: voir également cette réponse - https://stackoverflow.com/a/7950636/1068248 - pour savoir pourquoi il @autoreleasepooln'y a rien à voir avec ARC.

Mise à jour: J'ai jeté un œil sur les rouages ​​de ce qui se passe ici et je l'ai écrit sur mon blog . Si vous y jetez un œil, vous verrez exactement ce que fait ARC et comment le nouveau style @autoreleasepoolet comment il introduit une portée est utilisé par le compilateur pour déduire des informations sur ce qui retient, les versions et les versions automatiques sont nécessaires.

mattjgalloway
la source
12
Il ne supprime pas les retenues. Il les ajoute pour vous. Le comptage des références est toujours en cours, c'est juste automatique. D'où le comptage automatique des références :-D.
mattjgalloway
6
Alors pourquoi est-ce que ça n'ajoute pas @autoreleasepoolpour moi aussi? Si je ne contrôle pas ce qui est automatiquement publié ou publié (ARC le fait pour moi), comment dois-je savoir quand configurer un pool de libération automatique?
mk12
5
Mais vous avez le contrôle sur la destination de vos pools de libération automatique. Il y en a un qui entoure toute votre application par défaut, mais vous voudrez peut-être plus.
mattjgalloway
5
Bonne question. Il suffit de «savoir». Pensez à en ajouter un comme à la raison pour laquelle on pourrait, dans un langage GC, ajouter un indice à un garbage collector pour continuer et exécuter un cycle de collecte maintenant. Peut-être que vous savez qu'il y a une tonne d'objets prêts à être effacés, vous avez une boucle qui alloue un tas d'objets temporaires, donc vous «savez» (ou Instruments pourrait vous dire :) que l'ajout d'un pool de publication autour de la boucle serait un bonne idée.
Graham Perks
6
L'exemple de bouclage fonctionne parfaitement sans autorelease: chaque objet est désalloué lorsque la variable est hors de portée. L'exécution du code sans autorelease prend une quantité constante de mémoire et montre que les pointeurs sont réutilisés, et le fait de placer un point d'arrêt sur le dealloc d'un objet montre qu'il est appelé une fois à chaque fois dans la boucle, lorsque objc_storeStrong est appelé. Peut-être qu'OSX fait quelque chose de stupide ici, mais autoreleasepool est complètement inutile sur iOS.
Glenn Maynard
16

@autoreleasepoolne publie rien automatiquement. Il crée un pool de libération automatique, de sorte que lorsque la fin du bloc est atteinte, tous les objets qui ont été libérés automatiquement par ARC alors que le bloc était actif reçoivent des messages de libération. Le guide de programmation de la gestion avancée de la mémoire d' Apple l' explique ainsi:

À la fin du bloc de groupe de libération automatique, les objets qui ont reçu un message de libération automatique dans le bloc reçoivent un message de libération - un objet reçoit un message de libération pour chaque fois qu'il a reçu un message de libération automatique dans le bloc.

outis
la source
1
Pas nécessairement. L'objet recevra un releasemessage mais si le nombre de retenues est> 1, l'objet ne sera PAS désalloué.
andybons
@andybons: mis à jour; Merci. S'agit-il d'un changement par rapport au comportement pré-ARC?
sortie le
Ceci est une erreur. Les objets libérés par ARC recevront des messages de libération dès leur publication par ARC, avec ou sans pool de libération automatique.
Glenn Maynard
7

Les gens comprennent souvent mal ARC pour une sorte de ramassage des ordures ou similaire. La vérité est qu'après un certain temps, les gens d'Apple (grâce aux projets llvm et clang) ont réalisé que l'administration de la mémoire d'Objective-C (tous les retainset releases, etc.) pouvait être entièrement automatisée au moment de la compilation . C'est, juste en lisant le code, avant même qu'il ne soit exécuté! :)

Pour ce faire, il n'y a qu'une seule condition: nous devons suivre les règles , sinon le compilateur ne serait pas en mesure d'automatiser le processus au moment de la compilation. Donc, pour nous assurer que nous ne respectent pas les règles, nous ne sommes pas autorisés à écrire explicitement release, retainetc. Ces appels sont automatiquement injectés dans notre code par le compilateur. Par conséquent interne , nous avons encore autoreleases, retain, release, etc. Il est juste que nous ne avons pas besoin de les écrire plus.

Le A d'ARC est automatique au moment de la compilation, ce qui est bien meilleur qu'au moment de l'exécution, comme le garbage collection.

Nous l'avons toujours @autoreleasepool{...}parce que l'avoir n'enfreint aucune des règles, nous sommes libres de créer / vider notre piscine chaque fois que nous en avons besoin :).

nacho4d
la source
1
ARC est un GC comptant des références, pas un GC Mark-and-Sweep comme vous obtenez dans JavaScript et Java, mais c'est définitivement un garbage collection. Cela ne répond pas à la question - «vous pouvez» ne répond pas à la question «pourquoi devriez-vous». Tu ne devrais pas.
Glenn Maynard
3

C'est parce que vous devez toujours fournir au compilateur des indications sur le moment où il est sûr pour les objets libérés automatiquement de sortir de la portée.

DougW
la source
Pouvez-vous me donner un exemple du moment où vous auriez besoin de faire cela?
mk12
Donc, avant ARC, par exemple, j'avais un CVDisplayLink en cours d'exécution sur un thread secondaire pour mon application OpenGL, mais je n'ai pas créé de pool de libération automatique dans son runloop parce que je savais que je ne réalisais rien (ou n'utilisais pas de bibliothèques qui le font). Cela signifie-t-il que je dois maintenant ajouter @autoreleasepoolparce que je ne sais pas si ARC pourrait décider de publier automatiquement quelque chose?
mk12
@ Mk12 - Non. Vous aurez toujours un pool de libération automatique qui sera vidé à chaque fois autour de la boucle de fonctionnement principale. Vous ne devriez avoir besoin d'en ajouter un que si vous voulez vous assurer que les objets qui ont été libérés automatiquement sont vidés avant qu'ils ne le fassent autrement - par exemple, lors du prochain tour de la boucle d'exécution.
mattjgalloway
2
@DougW - J'ai jeté un coup d'œil sur ce que fait réellement le compilateur et j'ai blogué à ce sujet ici - iphone.galloway.me.uk/2012/02/a-look-under-arcs-hood- –-episode-3 /. J'espère qu'il explique ce qui se passe à la fois au moment de la compilation et à l'exécution.
mattjgalloway
2

Extrait de https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html :

Blocs et threads de pool de libération automatique

Chaque thread d'une application Cocoa gère sa propre pile de blocs de pool de libération automatique. Si vous écrivez un programme uniquement Foundation ou si vous détachez un thread, vous devez créer votre propre bloc de pool de libération automatique.

Si votre application ou thread a une longue durée de vie et génère potentiellement beaucoup d'objets auto-libérés, vous devez utiliser des blocs de pool de libération automatique (comme AppKit et UIKit le font sur le thread principal); sinon, les objets libérés automatiquement s'accumulent et votre empreinte mémoire augmente. Si votre thread détaché n'effectue pas d'appels Cocoa, vous n'avez pas besoin d'utiliser un bloc de pool de libération automatique.

Remarque: Si vous créez des threads secondaires à l'aide des API de thread POSIX au lieu de NSThread, vous ne pouvez pas utiliser Cocoa sauf si Cocoa est en mode multithreading. Cocoa entre en mode multithreading uniquement après avoir détaché son premier objet NSThread. Pour utiliser Cocoa sur des threads POSIX secondaires, votre application doit d'abord détacher au moins un objet NSThread, qui peut se fermer immédiatement. Vous pouvez tester si Cocoa est en mode multithreading avec la méthode de classe NSThread isMultiThreaded.

...

Dans le comptage automatique de références, ou ARC, le système utilise le même système de comptage de références que MRR, mais il insère la méthode de gestion de la mémoire appropriée qui vous appelle au moment de la compilation. Vous êtes fortement encouragé à utiliser ARC pour de nouveaux projets. Si vous utilisez ARC, il n'est généralement pas nécessaire de comprendre l'implémentation sous-jacente décrite dans ce document, même si cela peut être utile dans certaines situations. Pour plus d'informations sur ARC, consultez les notes de mise à jour de la transition vers ARC.

Raunak
la source
2

Les pools de libération automatique sont requis pour renvoyer les objets nouvellement créés à partir d'une méthode. Par exemple, considérez ce morceau de code:

- (NSString *)messageOfTheDay {
    return [[NSString alloc] initWithFormat:@"Hello %@!", self.username];
}

La chaîne créée dans la méthode aura un nombre de rétention de un. Maintenant, qui doit équilibrer ce compte de retenue avec une libération?

La méthode elle-même? Pas possible, il doit renvoyer l'objet créé, il ne doit donc pas le libérer avant de le renvoyer.

L'appelant de la méthode? L'appelant ne s'attend pas à récupérer un objet qui doit être libéré, le nom de la méthode n'implique pas qu'un nouvel objet est créé, il indique seulement qu'un objet est retourné et cet objet retourné peut être un nouvel objet nécessitant une libération mais il peut bien être un existant qui ne le fait pas. Ce que la méthode retourne peut même dépendre d'un état interne, de sorte que l'appelant ne peut pas savoir s'il doit libérer cet objet et il ne devrait pas s'en soucier.

Si l'appelant devait toujours libérer tous les objets retournés par convention, alors chaque objet non nouvellement créé devrait toujours être conservé avant de le renvoyer d'une méthode et il devrait être libéré par l'appelant une fois qu'il est hors de portée, sauf si il est renvoyé à nouveau. Cela serait très inefficace dans de nombreux cas car on peut éviter complètement de modifier les décomptes de rétention dans de nombreux cas si l'appelant ne libère pas toujours l'objet retourné.

C'est pourquoi il existe des pools de libération automatique, donc la première méthode deviendra en fait

- (NSString *)messageOfTheDay {
    NSString * res = [[NSString alloc] initWithFormat:@"Hello %@!", self.username];
    return [res autorelease];
}

Appeler autoreleaseun objet l'ajoute au pool de libération automatique, mais qu'est-ce que cela signifie vraiment, ajouter un objet au pool de libération automatique? Eh bien, cela signifie dire à votre système " Je veux que vous libériez cet objet pour moi, mais plus tard, pas maintenant; il a un compte de rétention qui doit être équilibré par une libération sinon la mémoire va fuir mais je ne peux pas le faire moi-même pour le moment, comme j'ai besoin que l'objet reste en vie au-delà de ma portée actuelle et que mon interlocuteur ne le fera pas non plus pour moi, il ne sait pas que cela doit être fait. Alors ajoutez-le à votre piscine et une fois que vous aurez nettoyé cela piscine, nettoyez également mon objet pour moi. "

Avec ARC, le compilateur décide pour vous quand conserver un objet, quand libérer un objet et quand l'ajouter à un pool de libération automatique, mais il nécessite toujours la présence de pools de libération automatique pour pouvoir renvoyer des objets nouvellement créés à partir de méthodes sans fuite de mémoire. Apple vient de faire quelques optimisations astucieuses sur le code généré, ce qui éliminera parfois les pools de libération automatique pendant l'exécution. Ces optimisations nécessitent que l'appelant et l'appelé utilisent ARC (rappelez-vous que le mélange d'ARC et de non-ARC est légal et également officiellement pris en charge) et si tel est réellement le cas ne peut être connu qu'au moment de l'exécution.

Considérez ce code ARC:

// Callee
- (SomeObject *)getSomeObject {
    return [[SomeObject alloc] init];
}

// Caller
SomeObject * obj = [self getSomeObject];
[obj doStuff];

Le code que le système génère peut se comporter comme le code suivant (c'est la version sûre qui vous permet de mélanger librement le code ARC et non-ARC):

// Callee
- (SomeObject *)getSomeObject {
    return [[[SomeObject alloc] init] autorelease];
}

// Caller
SomeObject * obj = [[self getSomeObject] retain];
[obj doStuff];
[obj release];

(Notez que la conservation / libération de l'appelant n'est qu'une retenue de sécurité défensive, ce n'est pas strictement nécessaire, le code serait parfaitement correct sans cela)

Ou il peut se comporter comme ce code, au cas où les deux seraient détectés pour utiliser ARC au moment de l'exécution:

// Callee
- (SomeObject *)getSomeObject {
    return [[SomeObject alloc] init];
}

// Caller
SomeObject * obj = [self getSomeObject];
[obj doStuff];
[obj release];

Comme vous pouvez le voir, Apple élimine l'atuorelease, donc également la libération d'objet retardée lorsque la piscine est détruite, ainsi que la conservation de sécurité. Pour en savoir plus sur la façon dont cela est possible et ce qui se passe réellement dans les coulisses, consultez cet article de blog.

Passons maintenant à la question réelle: pourquoi utiliserait-on @autoreleasepool?

Pour la plupart des développeurs, il ne reste plus qu'une seule raison aujourd'hui pour utiliser cette construction dans leur code et c'est de maintenir une faible empreinte mémoire le cas échéant. Par exemple, considérez cette boucle:

for (int i = 0; i < 1000000; i++) {
    // ... code ...
    TempObject * to = [TempObject tempObjectForData:...];
    // ... do something with to ...
}

Supposons que chaque appel à tempObjectForDatapeut créer un nouveau TempObjectqui est renvoyé autorelease. La boucle for créera un million de ces objets temporaires qui sont tous collectés dans le pool automatique actuel et une fois que ce pool est détruit, tous les objets temporaires sont également détruits. Jusqu'à ce que cela se produise, vous avez un million de ces objets temporaires en mémoire.

Si vous écrivez le code comme ceci à la place:

for (int i = 0; i < 1000000; i++) @autoreleasepool {
    // ... code ...
    TempObject * to = [TempObject tempObjectForData:...];
    // ... do something with to ...
}

Ensuite, un nouveau pool est créé à chaque exécution de la boucle for et est détruit à la fin de chaque itération de boucle. De cette façon, au plus un objet temporaire traîne en mémoire à tout moment malgré le fonctionnement de la boucle un million de fois.

Dans le passé, vous deviez souvent gérer vous-même les pools de relâchement automatique lors de la gestion des threads (par exemple en utilisant NSThread) car seul le thread principal a automatiquement un pool de libération automatique pour une application Cocoa / UIKit. Pourtant, c'est à peu près un héritage aujourd'hui, car aujourd'hui, vous n'utiliseriez probablement pas de threads pour commencer. Vous utiliseriez DispatchQueueles ou les GCD NSOperationQueueet ces deux derniers gèrent pour vous un pool de libération automatique de niveau supérieur, créé avant d'exécuter un bloc / une tâche et détruit une fois terminé.

Mecki
la source
-4

Il semble y avoir beaucoup de confusion sur ce sujet (et au moins 80 personnes qui sont probablement maintenant confuses à ce sujet et pensent qu'elles doivent saupoudrer @autoreleasepool autour de leur code).

Si un projet (y compris ses dépendances) utilise exclusivement ARC, alors @autoreleasepool n'a jamais besoin d'être utilisé et ne fera rien d'utile. ARC gérera la libération des objets au bon moment. Par exemple:

@interface Testing: NSObject
+ (void) test;
@end

@implementation Testing
- (void) dealloc { NSLog(@"dealloc"); }

+ (void) test
{
    while(true) NSLog(@"p = %p", [Testing new]);
}
@end

affiche:

p = 0x17696f80
dealloc
p = 0x17570a90
dealloc

Chaque objet de test est désalloué dès que la valeur est hors de portée, sans attendre la sortie d'un pool de libération automatique. (La même chose se produit avec l'exemple NSNumber; cela nous permet simplement d'observer le dealloc.) ARC n'utilise pas autorelease.

La raison pour laquelle @autoreleasepool est toujours autorisé est pour les projets mixtes ARC et non-ARC, qui n'ont pas encore complètement migré vers ARC.

Si vous appelez du code non ARC, il peut renvoyer un objet libéré automatiquement. Dans ce cas, la boucle ci-dessus fuirait, car le pool de libération automatique actuel ne sera jamais quitté. C'est là que vous voudrez mettre un @autoreleasepool autour du bloc de code.

Mais si vous avez complètement effectué la transition ARC, oubliez l'autoreleasepool.

Glenn Maynard
la source
4
Cette réponse est erronée et va également à l'encontre de la documentation ARC. votre preuve est anecdotique parce que vous utilisez une méthode d'allocation que le compilateur décide de ne pas autoriser. Vous pouvez très facilement voir que cela ne fonctionne pas si vous créez un nouvel initialiseur statique pour votre classe personnalisée. Créer ce initialiseur et de l' utiliser dans votre boucle: + (Testing *) testing { return [Testing new] }. Ensuite, vous verrez que dealloc ne sera appelé que plus tard. Ce problème est résolu si vous enveloppez l'intérieur de la boucle dans un @autoreleasepoolbloc.
Dima
@Dima Essayé sur iOS10, dealloc est appelé immédiatement après l'impression de l'adresse de l'objet. + (Testing *) testing { return [Testing new];} + (void) test { while(true) NSLog(@"p = %p", [self testing]);}
KudoCC
@KudoCC - Moi aussi, et j'ai vu le même comportement que vous. Mais, quand j'ai jeté [UIImage imageWithData]dans l'équation, alors, tout d'un coup, j'ai commencé à voir le autoreleasecomportement traditionnel , exigeant @autoreleasepoolde garder la mémoire de pointe à un niveau raisonnable.
Rob
@Rob Je ne peux pas m'empêcher d'ajouter le lien .
KudoCC