Autorelease-Pools sind erforderlich, um neu erstellte Objekte von einer Methode zurückzugeben. Betrachten Sie zB diesen Code:
- (NSString *)messageOfTheDay {
return [[NSString alloc] initWithFormat:@"Hello %@!", self.username];
}
Die in der Methode erstellte Zeichenfolge hat eine Aufbewahrungszahl von eins. Wer soll nun diese Anzahl mit einer Freigabe ausgleichen?
Die Methode selbst? Nicht möglich, es muss das erstellte Objekt zurückgeben, daher darf es nicht vor der Rückgabe freigegeben werden.
Der Aufrufer der Methode? Der Aufrufer erwartet nicht, dass ein Objekt abgerufen wird, das freigegeben werden muss. Der Methodenname bedeutet nicht, dass ein neues Objekt erstellt wird. Er gibt lediglich an, dass ein Objekt zurückgegeben wird, und dieses zurückgegebene Objekt kann ein neues Objekt sein, für das eine Freigabe erforderlich ist Nun, es gibt eine, die es nicht tut. Was die Methode zurückgibt, hängt möglicherweise sogar von einem internen Status ab, sodass der Aufrufer nicht wissen kann, ob er dieses Objekt freigeben muss und sich nicht darum kümmern muss.
Wenn der Aufrufer immer alle zurückgegebenen Objekte gemäß Konvention freigeben müsste, müsste jedes nicht neu erstellte Objekt immer beibehalten werden, bevor es von einer Methode zurückgegeben wird, und es müsste vom Aufrufer freigegeben werden, sobald es den Gültigkeitsbereich verlässt, es sei denn es wird wieder zurückgegeben. Dies wäre in vielen Fällen äußerst ineffizient, da in vielen Fällen eine Änderung der Aufbewahrungsanzahl vollständig vermieden werden kann, wenn der Aufrufer das zurückgegebene Objekt nicht immer freigibt.
Aus diesem Grund gibt es Autorelease-Pools, sodass die erste Methode tatsächlich wird
- (NSString *)messageOfTheDay {
NSString * res = [[NSString alloc] initWithFormat:@"Hello %@!", self.username];
return [res autorelease];
}
Der Aufruf autorelease
auf einem Objekt fügt sie dem Autofreigabepool, aber was bedeutet das wirklich Mittel, um ein Objekt zu dem Autofreigabepool hinzufügen? Nun, es bedeutet, Ihrem System zu sagen: " Ich möchte, dass Sie dieses Objekt für mich freigeben, aber zu einem späteren Zeitpunkt, nicht jetzt. Es hat eine Aufbewahrungszahl, die durch eine Freigabe ausgeglichen werden muss, da sonst Speicherplatz verloren geht, aber ich kann das nicht selbst tun." Im Moment, da ich das Objekt brauche, um über meinen derzeitigen Bereich hinaus am Leben zu bleiben, und mein Anrufer es auch nicht für mich tun wird, weiß er nicht, dass dies getan werden muss. Fügen Sie es also Ihrem Pool hinzu und sobald Sie es aufgeräumt haben Pool, auch mein Objekt für mich aufräumen. "
Mit ARC entscheidet der Compiler für Sie, wann ein Objekt beibehalten, wann ein Objekt freigegeben und wann es einem Autorelease-Pool hinzugefügt werden soll. Es sind jedoch weiterhin Autorelease-Pools erforderlich, um neu erstellte Objekte von Methoden zurückgeben zu können, ohne Speicher zu verlieren. Apple hat gerade einige raffinierte Optimierungen am generierten Code vorgenommen, die manchmal Autorelease-Pools zur Laufzeit eliminieren. Diese Optimierungen erfordern, dass sowohl der Anrufer als auch der Angerufene ARC verwenden (denken Sie daran, dass das Mischen von ARC und Nicht-ARC legal ist und auch offiziell unterstützt wird) und ob dies tatsächlich der Fall ist, kann nur zur Laufzeit bekannt sein.
Betrachten Sie diesen ARC-Code:
// Callee
- (SomeObject *)getSomeObject {
return [[SomeObject alloc] init];
}
// Caller
SomeObject * obj = [self getSomeObject];
[obj doStuff];
Der vom System generierte Code kann sich entweder wie der folgende Code verhalten (dies ist die sichere Version, mit der Sie ARC- und Nicht-ARC-Code frei mischen können):
// Callee
- (SomeObject *)getSomeObject {
return [[[SomeObject alloc] init] autorelease];
}
// Caller
SomeObject * obj = [[self getSomeObject] retain];
[obj doStuff];
[obj release];
(Beachten Sie, dass die Aufbewahrung / Freigabe im Anrufer nur eine defensive Sicherheitsaufbewahrung ist. Sie ist nicht unbedingt erforderlich. Ohne sie wäre der Code vollkommen korrekt.)
Oder es kann sich wie dieser Code verhalten, falls erkannt wird, dass beide zur Laufzeit ARC verwenden:
// Callee
- (SomeObject *)getSomeObject {
return [[SomeObject alloc] init];
}
// Caller
SomeObject * obj = [self getSomeObject];
[obj doStuff];
[obj release];
Wie Sie sehen können, eliminiert Apple die Atuorelease, also auch die verzögerte Objektfreigabe, wenn der Pool zerstört wird, sowie die Sicherheit. Um mehr darüber zu erfahren, wie das möglich ist und was wirklich hinter den Kulissen passiert, lesen Sie diesen Blog-Beitrag.
Nun zur eigentlichen Frage: Warum sollte man verwenden @autoreleasepool
?
Für die meisten Entwickler gibt es heute nur noch einen Grund, dieses Konstrukt in ihrem Code zu verwenden, nämlich den Speicherbedarf gegebenenfalls gering zu halten. Betrachten Sie zB diese Schleife:
for (int i = 0; i < 1000000; i++) {
// ... code ...
TempObject * to = [TempObject tempObjectForData:...];
// ... do something with to ...
}
Angenommen, jeder Aufruf von tempObjectForData
kann eine neue erstellen TempObject
, die automatisch freigegeben wird. Die for-Schleife erstellt eine Million dieser temporären Objekte, die alle im aktuellen Autorelease-Pool gesammelt werden. Erst wenn dieser Pool zerstört wird, werden auch alle temporären Objekte zerstört. Bis dahin haben Sie eine Million dieser temporären Objekte im Speicher.
Wenn Sie den Code stattdessen so schreiben:
for (int i = 0; i < 1000000; i++) @autoreleasepool {
// ... code ...
TempObject * to = [TempObject tempObjectForData:...];
// ... do something with to ...
}
Dann wird jedes Mal, wenn die for-Schleife ausgeführt wird, ein neuer Pool erstellt und am Ende jeder Schleifeniteration zerstört. Auf diese Weise bleibt zu jeder Zeit höchstens ein temporäres Objekt im Speicher hängen, obwohl die Schleife eine Million Mal ausgeführt wird.
In der Vergangenheit mussten Sie beim Verwalten von Threads (z. B. Verwenden NSThread
) häufig auch Autorelease-Pools selbst verwalten, da nur der Haupt-Thread automatisch einen Autorelease-Pool für eine Cocoa / UIKit-App hat. Dies ist heute jedoch so ziemlich ein Vermächtnis, da Sie heute wahrscheinlich zunächst keine Threads verwenden würden. Sie würden GCDs DispatchQueue
oder NSOperationQueue
's verwenden und diese beiden verwalten einen Autorelease-Pool der obersten Ebene für Sie, der vor dem Ausführen eines Blocks / einer Aufgabe erstellt und nach Abschluss zerstört wurde.