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 NSAutoreleasePool
s, 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
autorelease
ainsi querelease
. - 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
:- 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; }
. - Lorsque vous souhaitez créer un pool plus local, comme @mattjgalloway l'a montré dans sa réponse.
- 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
Réponses:
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
NSAutoReleasePool
par la@autoreleasepool
directive 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
@autoreleasepool
parce 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'autorelease
appels.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'
@autoreleasepool
intérieur de lafor
boucle externe, vous libéreriez 100000000 objets plus tard au lieu de 10000 à chaque fois autour de lafor
boucle externe .Mise à jour: voir également cette réponse - https://stackoverflow.com/a/7950636/1068248 - pour savoir pourquoi il
@autoreleasepool
n'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
@autoreleasepool
et 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.la source
@autoreleasepool
pour 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?@autoreleasepool
ne 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 source
release
message mais si le nombre de retenues est> 1, l'objet ne sera PAS désalloué.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
retains
etreleases
, 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
,retain
etc. Ces appels sont automatiquement injectés dans notre code par le compilateur. Par conséquent interne , nous avons encoreautorelease
s,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 :).la source
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.
la source
@autoreleasepool
parce que je ne sais pas si ARC pourrait décider de publier automatiquement quelque chose?Extrait de https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html :
...
la source
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
autorelease
un 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 à
tempObjectForData
peut créer un nouveauTempObject
qui 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 utiliseriezDispatchQueue
les ou les GCDNSOperationQueue
et 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é.la source
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.
la source
+ (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@autoreleasepool
bloc.+ (Testing *) testing { return [Testing new];} + (void) test { while(true) NSLog(@"p = %p", [self testing]);}
[UIImage imageWithData]
dans l'équation, alors, tout d'un coup, j'ai commencé à voir leautorelease
comportement traditionnel , exigeant@autoreleasepool
de garder la mémoire de pointe à un niveau raisonnable.