09.06.2008

Autorelease Pool (ARP)

Mit dem Autorelease-Pool (ARP) lassen sich viele Probleme in Bezug auf die Speicherfreigabe lösen. Wird beispielsweise ein Objekt erzeugt und es ist nicht klar, wie lange es benötigt wird und wer es zu gegebenen Zeit wieder deallokiert, d.h. den Speicher freigibt, dann wird der ARP eingesetzt.

Die folgende Klasse Car repräsentiert ein Auto mit einer Farbe und einer Marke. Die Header-Datei sieht so aus:

#import <Cocoa/Cocoa.h>

@interface Car : NSObject {
	NSString* brand; //Car brand
	NSString* color; //Car color
}

//Init
- (Car*)initWithBrand:(NSString*)newBrand color:(NSString*)newColor;

// getter and setter for brand
- (NSString*)brand;
- (void)setBrand:(NSString*)newBrand;

// getter and setter for color
- (NSString*)color;
- (void)setColor:(NSString*)newColor;

@end
					

Also nichts besonderes. Die Implementierung dazu sieht folgendermaßen aus:

#import "Car.h"

@implementation Car

- (NSString*)brand {
	return brand;
}

- (void)setBrand:(NSString*)newBrand {
	if(newBrand != brand) {
		[brand release];
		brand = [newBrand retain];
	}
}

- (NSString*)color {
	return color;
}

- (void)setColor:(NSString*)newColor {
	if(newColor != color) {
		[color release];
		color = [newColor retain];
	}
}

- (Car*)initWithBrand:(NSString*)newBrand color:(NSString*)newColor {
	self = [super init];
	[self setBrand:newBrand];
	[self setColor:newColor];
	NSLog(@"initWithBrand Brand:%@  Color:%@", [self brand], [self color]);
	return self;
}

- (void) dealloc {
	NSLog(@"Dealloc Car");
	[self setBrand:nil];
	[self setColor:nil];
	[super dealloc];
}

@end
					

Damit zu sehen ist, wann die Initialisierung und die Deallokierung durchgeführt wird, habe ich ein paar Log-Ausgaben ergänzt.

Normalerweise würde der Programmcode folgendermaßen aussehen:

#import <Foundation/Foundation.h>
#import "Car.h"


int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

	Car* car;
	car = [[Car alloc]initWithBrand:@"BMW" color:@"Black"];
	[car release];

    [pool release];
    return 0;
}

Der Speicher für Car wird allokiert und das Objekt wird initialisiert. Wenn es nicht mehr benötigt wird, dann wird es mit release wieder freigegeben.

Eine andere Variante ist im Folgenden zu sehen:

	#import <Foundation/Foundation.h>
	#import "Car.h"


	int main (int argc, const char * argv[]) {
	    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

		Car* car;
		car = [[[Car alloc]initWithBrand:@"BMW" color:@"Black"]autorelease];
		NSLog(@"Show brand %@ and color %@", [car brand], [car color]);

	    [pool release];

		NSLog(@"End of processing...");
	    return 0;
	}
	

Hier wird ein autorelease an das Objekt geschickt. Wenn jetzt der pool freigebenen wird, dann wird auch car freigegeben.

Die Ausgabe zu dem Programm auf der Konsole sieht folgendermaßen aus:

2008-06-09 10:24:20.573 TestProject[49063:10b] initWithBrand Brand:BMW  Color:Black
2008-06-09 10:24:20.587 TestProject[49063:10b] Show brand BMW and color Black
2008-06-09 10:24:20.592 TestProject[49063:10b] Dealloc Car
2008-06-09 10:24:20.597 TestProject[49063:10b] End of processing...
			

Es ist zu sehen, daß das Objekt deallokiert wird, sobald der Pool freigegeben wird.

09.06.2008

Convenience Allocator mit dem Autorelease-Pool

Eine einfache und elegante Methode das "Aufräumen" von Objekten vor dem Benutzer einer Klasse zu verbergen ist der Convenience Allocator. Dies ist eine Art Factory (siehe Factory Pattern)., die als Hilfsmittel den Autorelease-Pool (ARP) verwendet um den allokierten Speicher wieder freizugeben. Der folgende Code zeigt eine Klasse die eine solche Factory-Methode anbietet:

#import "Car.h"

@implementation Car

+ (Car*)carWithBrand:(NSString*)brand color:(NSString*)color {
	return [[[Car alloc]initWithBrand:brand color:color]autorelease];
}

- (NSString*)brand {
	return brand;
}

- (void)setBrand:(NSString*)newBrand {
	if(newBrand != brand) {
		[brand release];
		brand = [newBrand retain];
	}
}

- (NSString*)color {
	return color;
}

- (void)setColor:(NSString*)newColor {
	if(newColor != color) {
		[color release];
		color = [newColor retain];
	}
}

- (Car*)init {
	self = [super init];
	if(self) {
		[self setBrand:[NSString string]];
		[self setColor:[NSString string]];
	}
	return self;
}

- (Car*)initWithBrand:(NSString*)newBrand color:(NSString*)newColor {
	self = [super init];
	[self setBrand:newBrand];
	[self setColor:newColor];
	NSLog(@"initWithBrand Brand:%@  Color:%@", [self brand], [self color]);
	return self;
}

- (void) dealloc {
	NSLog(@"Dealloc Car");
	[self setBrand:nil];
	[self setColor:nil];
	[super dealloc];
}

@end

Die Funktion carWithBrand allokiert Speicher für Car und ruft die Init-Funktion auf. Außerdem wird die Nachricht autorelease an das Objekt geschickt, damit der Speicher von ARP freigegeben werden kann, wenn das Objekt nicht mehr verwendet wird. Auf die gleiche Weise funktioniert auch die Methode string der Klasse NSString. Normalerweise müßte nämlich ein release auf das durch string erzeugte Objekt aufgerufen werden. Dies ist aber nicht notwendig, weil string dem Objekt ein autorelease geschickt hat.