ObjectiveCeeds › Foundation: Vom Callstack, der RunLoop und dem AutoreleasePool

Foundation: Vom Callstack, der RunLoop und dem AutoreleasePool

Von Manfred Kreß
Der AutoreleasePool
Seite 3 von 3



Eigentlich funktioniert die Speicherverwaltung unter Cocoa auch sehr gut ohne Autorelease Pool. Eine kleine Versuchsklasse: Das Interface:
#import <Foundation/Foundation.h>


@interface OCLogDealloc : NSObject {

}

@end
Und die Implementation:
@implementation OCLogDealloc
- (id) init
{
	self = [super init];
	if (self != nil) {
		
	}
	return self;
}

- (void) dealloc
{
	NSLog(@"ByeBye");
	[super dealloc];
}

@end
OCLogDealloc logt eigentlich nur, wenn seine dealloc aufgerufen wird. Und hier das Experiment:
int main (int argc, const char * argv[]) {
   
	NSMutableArray *array = [[NSMutableArray alloc] init];
	
	OCLogDealloc *object = [[OCLogDealloc alloc] init];
	[array addObject: object];
	[object release];
	
	NSLog(@"object ist noch da...");
	
	[array removeAllObjects];
	
	NSLog(@"object sollte dealloc getriggert haben");
	
	[array release];
	
    return 0;
}
Ausgabe:
2009-12-22 13:40:29.163 Autorelease[2364:a0b] object ist noch da...
2009-12-22 13:40:29.167 Autorelease[2364:a0b] ByeBye
2009-12-22 13:40:29.168 Autorelease[2364:a0b] object sollte dealloc getriggert haben
Ein Objekt wird initialisiert (RetainCount=1), wird einem Array übergeben das es retained (RetainCount=2), einmal released für die init (RetainCount=1) und sobald das Objekt wieder vom Array genommen wird, also vom Array wieder released wird ist der RetainCount 0 und der Speicher wird frei gegeben. Wozu dann der Autorelease Pool? Das Problem sind Objekte die in einer Funktion oder Methode erzeugt werden und an einen Anwender zurück gegeben werden.
NSMutableArray * OCEmptyArray ()
{
	return [[NSMutableArray alloc] init];
}
Der Release der für unsere eigene Initialisierung geschickt werden muss, kann nicht stattfinden, da das Objekt seinen Speicher frei geben müsste, bevor es mit return zurückgegeben wird, was wenig Sinn macht. Man müsste sich darauf verlassen, das der Anwender dem das Objekt zurückgegeben wird, dieses auch released. Per Konvention könnte man das festlegen. Aber da wir schon genug Mühe damit haben unsere eigenen Objekte auch wieder zu releasen, trauen wir einem „Fremden“ in dieser Hinsicht besser nicht über den Weg… Schaut man sich die Sache mit dem Blick auf den Callstack an, sieht das so aus: ObjectiveCeeds: Callstack Autorelease Die Funktion/Methode in der das Objekt erzeugt wird ist ganz oben auf dem Callstack. „Darunter“ ist die aufrufende Funktion des Benutzers, der das Objekt anfordert und darunter befinden sich dann wiederum die Funktionen des Frameworks usw. Nun wäre es doch eine gute Idee, den Release der eigentlich ganz oben kommen müsste dann zu schicken, wenn der Callstack wieder aus der Funktion des Anwenders zurückkehrt und dieser mit dem Objekt fertig ist, sprich es nicht mehr braucht, bzw. es einem anderen Objekt übergeben hat (retained). Und genau das macht der Autorelease Pool. Auch er „wartet unten“, bis der Callstack wieder zu ihm zurück kehrt. „Oben“ wird das Objekt mit autorelease dem Pool hinzugefügt. Kommt der Callstack wieder auf der Ebene des „Autorelease Pool Handlers“ an, wird allen Objekten im Pool ein Release geschickt und sie werden aus dem Pool entfernt – der RetainCount ist ausgeglichen. ObjectiveCeeds: AutoreleasePool Der autorelease Pool schaut nicht, ob es noch irgendwelche Zeiger auf das Objekt existieren und gibt dann den Speicher frei, dann wäre es eine Garbage Collection. Es wird einfach jedes Objekt einmal released. Genau wie die RunLoop, ist auch der AutoreleasePool in einen Callstack „eingebaut“. Daraus ergibt sich wieder ein Problem. Werden in einer Anwenderfunktion bzw. Methode sehr viele autoreleaste Objekte erzeugt, läuft der Speicher des Rechners voll. Warum? Weil der Callstack nicht zum AutoreleasePool zurückkehrt und die Objekte den release nicht erhalten. Also bleibt der Speicher während der ganzen Zeit belegt. Der Ausweg sind eigene Pools. Es kann beliebig viele AutoreleasePools geben. Auch diese liegen wieder auf einem Stapel. Bekommt ein Objekt ein autorelease geschickt, wird es dem obersten Pool hinzugefügt. Wird dieser released oder mit drain geleert, bekommen alle Objekte ihren Release geschickt.
	
	for (NSInteger i=0; i<10000; i++)
	{
		NSAutoreleasePool *myPool = [[NSAutoreleasePool alloc] init];
		
		for (NSInteger j=0; j<10000; j++)
		{
			NSString *autoreleasedString = [NSString stringWithFormat: @"i: %i j: %j" , i,j];
			NSLog(@"%@" , autoreleasedString);
		}
		
		[myPool release];
 	}
In dieser Schleife, wird z.B. sicher gestellt, das nicht mehr als 10000 Objekte im Pool landen. Würde man hier keinen gekapselten AutoreleasePool verwenden, würden 1000000000 Objekte erzeugt werden. Der Speicher würde erst frei gegeben werden, wenn beide Schleifen durchlaufen sind. D.h. wenn dann auch die Methode in der sich die Schleife befindet beendet wäre. Die zweite Möglichkeit wäre natürlich die Objekte von Hand zu releasen - wenn das möglich ist.

« vorige Seite