ObjC-Memory Management

From Medien Wiki
 WICHTIG: Diese Seite handelt von mittlerweile mehr oder weniger obsolet gewordenem Memory Management.
 Unter Punkt 6. ARC wird die aktuelle Vorgehensweise beschrieben!


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.jpg
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!


ARC

Was ist ARC

Seit dem LLVM 3.0 Compiler (enthalten im iOS 5 SDK) gibt es Automated Reference Counting, das auf Compiler-Ebene automatisiert retain-/release calls einfügt. Demnach müssen alle obigen Regeln eigentlich nicht mehr beachtet werden, was eine unglaubliche Erleichterung für den Programmierer darstellt. Die Kenntnis über die Speicherverwaltung ist zwar noch von Vorteil, aber man spart sehr viele Zeilen Code und die Gefahr eines Programmabsturzes wird deutlich geringer.

Arc besteht im Prinzip aus nur wenigen Schlüsselwörtern. Bis auf __weak sind dabei alle schon in ObjC vorhanden und daher abwärtskompatibel:

  • __strong
  • __weak (>iOS5!)
  • __unsafe_unretained
  • __bridge ( inkl. __bridge_retained und __bridge_transfer)
  • __autoreleasing (@autorelease)


Vorteile

  • ARC ist kein Garage Collector, es passiert nichts zur Laufzeit
  • Der Code wird auf Compiler-Ebene optimiert und läuft daher schneller als vorher!
  • ARC ist zu iOS4 und Snow Leopard 10.6 abwärtskompatibel
  • Crash-Safe: deutlich weniger Abstürze wegen Speicherproblemen
  • Weniger Code
  • Der Programmierer kann sich auf das Wesentliche konzentrieren


Beispiel Deklaration

So wird aus folgendem Beispiel...

MyObject : NSObject {
    NSString *myName;
    id<MyProtocol> delegate;
    int myNumber;
}

@property(nonatomic, retain) NSString* myName;
@property(nonatomic, assign) id<MyProtocol> delegate;
@property(nonatomic) int myNumber;

...dieser Code mit ARC:

MyObject : NSObject {
    NSString __strong *myName;
    id<MyProtocol> delegate;
    int myNumber;
}

@property(nonatomic, strong) NSString* myName;
@property(nonatomic, assign) id<MyProtocol> delegate;
@property(nonatomic) int myNumber;


Aktuelles Beispiel (März 2013)

...oder noch einfacher: (und im Augenblick die empfohlene Vorgehensweise)

MyObject : NSObject { }

@property(nonatomic, strong) NSString* myName;
@property(nonatomic, assign) id<MyProtocol> delegate;
@property(nonatomic) int myNumber;

Bei letzterem Beispiel werden alle Instanzvariablen durch Properties ersetzt. D.h. man muss sie mit self. ansprechen (also self.myNumber anstatt myNumber).


Beispiel no more retain/release

Mit ARC gibt es nun einfach kein retain mehr; dies ist mustergültiger Code:

id myObj = [[MyClass alloc] init];
self.myProperty = myObj;
// no more [myObj release];
return;

Auch entfallen manche Methoden komplett, es gibt keinen Grund mehr -(void)dealloc zu überschreiben.


Voraussetzungen

Mindest-Voraussetzungen zum Einsatz von ARC:

  • Apple LLVM compiler 3.0
    • enhalten im iOS 5 SDK bzw. Mac OS X 10.5. SDK mit Xcode 4
  • Bei bestehenden Projekten kann der Refactor-Assistent zum Migrieren genutzt werden (Achtung: u.U. muss das .git Verzeichnis temporär verschoben werden)

Darüber hinaus ist ARC zu iOS 4.x abwärtskompatibel, wenn man:

  • kein __weak benutzt sondern __unsafe_unretained

Siehe auch:


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.