ObjC-Memory Management

From Medien Wiki

Retain-Count

Das Memory-Management von Cocoa basiert auf einem Retain-Count Mechanismus. Anders als z.B. bei C (malloc & free), gibt es in Objective-C eine klar geregelte Verantwortung für den Lebenszyklus des Objektes:

Ownership

  • Wer das Objekt erstellt (also mit alloc Speicher dafür reserviert) =>
  • ist der "Creator" und muss die Verantwortung für die Speicherfreigabe übernehmen (also mit release den Speicher wieder freigeben).

Jedes Objekt hat einen sog. retain count, der normalerweise bei der Erstellung des Objektes (z.B. mit alloc oder copy) einen Wert von 1 hat. Wenn nun dieses Objekt von einem anderen Objekt als seinem Eigentümer für längere Berechnungen oder als eigene Instanz-Variable benötigt wird, so kann man [obj retain] aufrufen. Damit steigt der retain-count dieses Objektes um 1 auf 2. Das Objekt wird gelöscht (bzw. der [obj dealloc] aufgerufen), wenn der retain-count auf 0 zurück geht.

Wichtig:

Der Programmierer muss dafür sorgen, dass seine retain/release bzw. alloc/release bzw. copy/release Aufrufe ausgeglichen sind. Ansonsten kommt es zu Leaks (Zombie-Objekte) oder zu einem Crash, wenn ein Objekt per release freigegeben wird, das es nicht mehr gibt.

Folgende Aufrufe erfordern einen späteren release, weil sie den retain-count um jeweils +1 erhöhen:

  • alloc (owner)
  • copy (owner)
  • new (owner, selten verwendet)
  • retain

Autorelease erhöht zwar den retain-count ebenfalls +1, in der nächsten Runloop wird der retain-count allerdings automatisch um −1 verringert. Deshalb ist hierfür kein release mehr erforderlich.


Beispiel

Rechenbeispiel zum retain-count (kein wirklich sinnvolles Code-Beispiel!)

NSString *myObj; // retaincount: 0
myObj = [[NSString alloc] initWithString:@"hello"]; // retaincount: 1
    [myObj retain]; // retaincount: 2
    [myObj release]; // retaincount: 1

    NSString *newString = [myObj copy]; // retaincount myObj: unverändert (1), newString: 1
    [newString autorelease]; // retaincount: 1; next loop retaincount: 0
[myObj release]; // retaincount: 0

Alloc/Init/Copy/Retain

NSString *newString = [[NSString alloc] init];
NSString *anotherString = [newString copy];
NSString *aThirdString = [anotherString retain];
  • alloc (allocate) reservier Speicher für ein Objekt (und macht sonst nichts weiter, deshalb sieht man alloc immer nur in Verbindung mit init: [[obj alloc] init] oder [[obj alloc] initWithSomething:...]
    • alloc erstellt ein Objekt mit einem anfänglichen retain-count von 1
  • init (initialize) initialisiert das Objekt
    • init erhöht den retain-count nicht!
  • copy kopiert das Objekt und erstellt ein neues, eigenständiges Objekt mit einem eigenen Speicherbereich
    • copy erstellt ein Objekt mit einem anfänglichen retain-count von 1
  • retain sorgt dafür, dass das Objekt auf jeden Fall erhalten bleibt. Es wird kein neues Objekt erstellt.
    • retain erhöht den retain-count um 1.
  • autorelease sendet ein release in der nächsten Run-Loop. Der retain-count bleibt im aktuellen Run-Loop erhalten.
    • autorelease verringert den retain-count um 1 in der nächsten Programmschleife

Autorelease

Die Run Loop wird mit dem Start des Programms erstellt. Objekte, die eine autorelease-Nachricht erhalten, werden in den sog. Autorelease-Pool aufgenommen und warten auf die nächste Schleife. Erst zu Beginn der nächsten Run-Loop erhalten die Objekte im Pool eine release-Nachricht und werden aus dem Pool entfernt. Damit wird sichergestellt, dass das Objekt während der aktuellen Schleife noch vorhanden und gültig ist. Innerhalb einer Runloop-Schleife sind wir z.B. wenn ein Event (Mausklick) erfolgt ist.

 
Autorelease-Pool Funktionsweise
Bildnachweis: © basiert auf Stanford iPhone Application Programming 2009 (iTunes U), Slide from Lecture 3

Beispiel

Interface (file.h)

@interface MyClass:NSObject {
    NSString *name;
    int age;
    id delegate;
}

@property (nonatomic, retain) id delegate;

// Hinweis: name und age würden normalerweise auch einfacher als properties verwendet!

-(NSString*)name;
-(void)setNewName:(NSString*)newName;

-(int)age;
-(void)setAge:(int)newAge;

@end


Implementation (file.m)

#import "file.h"

@implementation MyClass

@synthesize delegate;

-(id)init {
    self = [super init];
    if(self) {
        [self setName:@"Barnie"];
        [self setAge:30];
    }
}

-(NSString*)name { return name; }
-(void)setName:(NSString*)newName {
    if(newName != name) {
        [name release];
        [newName retain];
        name = newName;
    }
}

-(int)age { return age; }
-(void)setAge:(int)newAge { age = newAge; }

-(void)dealloc {
    [delegate release]; // retain-property!
    [self setName:nil]; // release wird im Setter gesendet!
    // age braucht keinen release weil int ist kein Objekt!
}

@end


Fragen:

  • was ist ein Accessor?
  • was passiert im dealloc wenn man schreibt: name = nil;?
  • wo ist der Unterschied:
    • [self setName:nil];
    • self.name = nil;
    • name = nil;


Regeln

  • Genau geregelte Verantwortlichkeiten:
  • Wer alloc, new, copy, retain verwendet, muss auch jeweils 1x release senden
  • Wer nicht alloc, new, copy, retain aufruft, darf nicht release aufrufen
  • Niemals dealloc selbst aufrufen (es sei denn man überschreibt die Methode und ruft [super dealloc] auf!

Links




Diese Seite ist Teil des Werkmoduls iOS Development von Michael Markert für Interface Design / Fakultät Medien an der Bauhaus-Universität Weimar.