web-dev-qa-db-fra.com

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

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

@autoreleasepool {
    …
}

Ma question est la suivante: pourquoi en aurais-je besoin alors que je ne suis pas censé publier/autoreleasing manuellement?


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

Nouvelle syntaxe:

@autoreleasepool { … } Est une nouvelle syntaxe pour

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

Plus important encore:

  • ARC utilise autorelease ainsi que release.
  • Pour ce faire, il faut un pool de mainlevée automatique.
  • ARC ne crée pas le pool de libération automatique pour vous. Cependant:
    • Le fil principal de chaque application Cocoa contient déjà un pool de libération automatique.
  • Vous voudrez peut-être utiliser @autoreleasepool À deux occasions:
    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, tel que myRunLoop(…) { @autoreleasepool { … } return success; }.
    2. Lorsque vous souhaitez créer un pool plus local, comme l'a indiqué @mattjgalloway dans sa réponse.
188
mk12

ARC ne supprime pas les retenues, les versions et les autoreleases, il ajoute simplement celles qui sont requises pour vous. Il reste donc des appels à retenir, des appels à libérer, des appels à libération automatique et des pools de libération automatique.

L’un des autres changements qu’ils ont apportés au nouveau compilateur Clang 3.0 et à ARC est qu’ils ont remplacé NSAutoReleasePool par le @autoreleasepool directive du compilateur. De toute façon, NSAutoReleasePool a toujours été un "objet" spécial et ils l'ont fait pour que la syntaxe d'utilisation n'en soit pas confondue avec un objet, de sorte que ce soit généralement un peu plus simple.

Donc, fondamentalement, vous avez besoin de @autoreleasepool car il reste encore des pools de libération automatique à craindre. Vous n'avez simplement pas à vous soucier d'ajouter des appels autorelease.

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 complexe, bien sûr, mais si vous n'aviez pas le @autoreleasepool à l'intérieur de la boucle for- extérieure, vous libérerez plus de 100 000 objets au lieu de 10 000 à chaque fois autour de la boucle extérieure for.

Mise à jour: Voir aussi cette réponse - https://stackoverflow.com/a/7950636/1068248 - pourquoi @autoreleasepool n'a rien à voir avec ARC.

Mise à jour: J'ai jeté un coup d'œil aux éléments internes de ce qui se passe ici et l'a écrit sur mon blog . Si vous y jetez un coup d'œil, vous verrez exactement ce que fait ARC et comment le nouveau style @autoreleasepool et la manière dont il introduit une portée est utilisée par le compilateur pour déduire des informations sur les éléments conservés, publiés et autoreleases.

211
mattjgalloway

@autoreleasepool ne libère rien automatiquement. Il crée un pool de libération automatique de sorte que, lorsque la fin du bloc est atteinte, tous les objets libérés automatiquement par ARC pendant que le bloc était actif se verront envoyer des messages de libération. Le Guide de programmation de gestion de la mémoire avancée de Apple l'explique ainsi:

À la fin du bloc de pool de libération automatique, les objets ayant reçu un message de libération automatique dans le bloc se voient envoyer un message de libération. Un objet reçoit un message de libération à chaque fois qu'un message de libération automatique est envoyé dans le bloc.

15
outis

Les gens comprennent souvent mal ARC pour une sorte de collecte des ordures ou similaire. La vérité est qu’après un certain temps, les utilisateurs de Apple (grâce aux projets llvm et clang)) ont compris que l’administration de la mémoire d’Objective-C (tous les retains et releases, etc.) peuvent être entièrement automatisés à le temps de compilation. Ceci, 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 pourrait pas automatiser le processus au moment de la compilation. Donc, pour nous assurer que jamais enfreignons les règles, nous ne sommes pas autorisés à écrire explicitement release, retain, etc. Ces appels sont automatiquement injecté dans notre code par le compilateur. Donc en interne nous avons toujours autoreleases, retain, release, etc. C'est juste que nous n'avons plus besoin de les écrire.

Le A de ARC est automatique à la compilation, ce qui est bien meilleur qu’au moment de l’exécution, comme le ramassage des ordures.

Nous avons toujours @autoreleasepool{...} _ parce qu’il ne déroge à aucune des règles, nous sommes libres de créer/vider notre pool quand nous en avons besoin :).

7
nacho4d

C'est parce que vous devez toujours fournir au compilateur des indications sur le moment opportun pour que les objets auto-libérés soient hors de portée.

3
DougW

Cité de https://developer.Apple.com/library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html :

Blocs et threads de pool Autorelease

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

Si votre application ou votre thread a une longue durée de vie et génère potentiellement un grand nombre d'objets autoreleased, vous devez utiliser des blocs de pool autorelease (tels que AppKit et UIKit do sur le thread principal); sinon, les objets autoreleased s'accumulent et votre empreinte mémoire augmente. Si votre thread détaché ne passe pas d'appels Cocoa, vous n'avez pas besoin d'utiliser un bloc de pool de relâchement automatique.

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

...

Dans Automatic Count Count, ou ARC, le système utilise le même système de comptage de références que MRR, mais il insère les appels de méthode de gestion de la mémoire appropriés pour vous au moment de la compilation. Nous vous encourageons fortement à utiliser ARC pour les 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, bien que cela puisse être utile dans certaines situations. Pour plus d'informations sur ARC, voir Notes de publication de la transition vers ARC.

1
Raunak

Les pools de libération automatique sont requis pour renvoyer des objets nouvellement créés à partir d'une méthode. Par exemple. Considérons 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 retenues égal à un. Maintenant, qui doit équilibrer qui compte avec une libération?

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

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 la création d'un nouvel objet, il indique simplement qu'un objet est renvoyé et que cet objet renvoyé peut être un nouvel objet nécessitant une libération, mais il peut aussi bien être un existant qui ne le fait pas. Le résultat de la méthode 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 avoir à s'en soucier.

Si l'appelant devait toujours libérer tous les objets retournés selon la convention, chaque objet non créé devrait toujours être conservé avant de le renvoyer depuis une méthode et il devrait être libéré par l'appelant dès qu'il sort du champ d'application, à moins que il est retourné à nouveau. Cela serait très inefficace dans de nombreux cas, car on peut éviter complètement de modifier les décomptes de conservation si l'appelant ne libère pas toujours l'objet renvoyé.

C’est la raison pour laquelle il existe des pools de libération automatique, de sorte que la première méthode deviendra

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

L'appel de autorelease sur un objet l'ajoute au pool de libération automatique, mais qu'est-ce que cela signifie réellement, l'ajout d'un objet au pool de libération automatique? Eh bien, cela signifie dire à votre système " Je veux que vous relâchiez cet objet pour moi, mais à un moment ultérieur, pas maintenant; il a un nombre de retenues qui doit être contrebalancé par une libération, sinon la mémoire va fuir mais je ne peux pas le faire moi-même pour le moment, car j'ai besoin que l'objet reste en vie au-delà de mon champ actuel et mon interlocuteur ne le fera pas pour moi non plus, il n'a pas la moindre idée que cela doit être fait. à votre piscine et une fois que vous nettoyez cette 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 autorelease, mais cela nécessite tout de même la présence de pools autorelease pour pouvoir renvoyer des objets nouvellement créés à partir de méthodes sans perte de mémoire. Apple vient d’optimiser astucieusement le code généré, ce qui éliminera parfois les pools de libération automatique lors de l’exécution. Ces optimisations exigent que l’appelant et l’appelé utilisent ARC (rappelez-vous qu’il est préférable de mélanger ARC et non ARC est légal et également officiellement supporté) et si tel est le cas, cela 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 généré par le système peut se comporter comme le code suivant (c'est-à-dire la version sûre qui vous permet de mélanger librement du 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 le maintien/libération dans l'appelant est simplement un maintien de sécurité défensif, ce n'est pas strictement requis, le code serait parfaitement correct sans cela)

Ou bien il peut se comporter comme ce code, au cas où les deux détectent qu'ils utilisent 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 constater, Apple élimine l’atuorelease, donc également la libération retardée de l’objet lorsque le pool est détruit, ainsi que la sécurité conservée. Pour en savoir plus sur la manière dont cela est possible et ce qui se passe réellement dans les coulisses, consultez ce blog.

Passons maintenant à la question actuelle: pourquoi utiliserait-on @autoreleasepool?

Pour la plupart des développeurs, il ne reste plus aujourd'hui qu'une seule raison d'utiliser cette construction dans leur code: conserver au minimum l'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 à tempObjectForData puisse créer un nouveau TempObject renvoyé autorelease. La boucle for créera un million d'objets temp qui seront tous collectés dans le pool automatique actuel et une fois que ce pool aura été détruit, tous les objets temp seront également détruits. En attendant, vous avez un million d'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 fois que la boucle for est exécutée et est détruit à la fin de chaque itération de la boucle. De cette façon, au plus un objet temporaire reste en mémoire à tout moment malgré la boucle exécutée un million de fois.

Dans le passé, vous deviez souvent aussi gérer vous-même les autorelease lors de la gestion des threads (par exemple, en utilisant NSThread), car seul le thread principal possède automatiquement un pool d'auto-relâchement pour une application Cocoa/UIKit. Pourtant, il s’agit plutôt d’un héritage d’aujourd’hui, car aujourd’hui, vous n’utiliseriez probablement pas de fil de discussion. Vous utiliseriez GCD DispatchQueue ou NSOperationQueue et ces deux-là géreront pour vous un pool de libération automatique de niveau supérieur, créé avant l'exécution d'un bloc/tâche, puis détruit.

1
Mecki