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:
- (SomeObject *)getSomeObject {
return [[SomeObject alloc] init];
}
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):
- (SomeObject *)getSomeObject {
return [[[SomeObject alloc] init] autorelease];
}
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:
- (SomeObject *)getSomeObject {
return [[SomeObject alloc] init];
}
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++) {
TempObject * to = [TempObject tempObjectForData:...];
}
Supposons que chaque appel à tempObjectForData
peut créer un nouveau TempObject
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 {
TempObject * to = [TempObject tempObjectForData:...];
}
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 DispatchQueue
les ou les GCD NSOperationQueue
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é.