Schritt für Schritt - Entwickeln für OS X und iOS
Schritt für Schritt - Entwickeln für OS X und iOS
Klassen, Teil 2 - Speicherverwaltung

Im letzten Kapitel haben Sie schon Objekte der Personenklasse erzeugt, ohne dass in dem Beispiel näher darauf eingegangen wurde, was dort denn nun genau passiert. Das werden wir an dieser Stelle nun nachholen.
Objekte zu instanzieren, also aus Klassen Objekte zu erzeugen, ist eines der Hauptmerkmale der objektorientierten Programmierung und geschieht in Objective-C in mehreren Schritten.
Person *myPerson;
Dieser Aufruf deklariert ein Person-Objekt mit dem Namen myPerson. Der Stern vor einem Variablennamen besagt immer, dass es sich hierbei um einen Objektzeiger handelt. Objektzeiger sind Zeiger auf Speicheradressen im Speicher des Computers. Dies bedeutet, myPerson beinhaltet gar kein Person-Objekt, sondern nur die Speicheradresse, wo sich ein Person-Objekt im Speicher befindet. Der Speicher für ein Objekt muss in einem separaten Schritt angelegt und reserviert werden
[Person alloc];
Es ist der Aufruf alloc, vom englischen allocate, der tatsächlich Speicher für ein Objekt anlegt. Allerdings ergibt der Aufruf in der oben gezeigten Form nicht besonders viel Sinn. Zwar wird Speicher für ein Objekt, in diesem Fall von der Klasse Person angelegt, aber das Programm hat keinerlei Informationen, wo sich denn dieser Speicherbereich befindet. Passender Weise hat dieser Aufruf einen Rückgabewert, der die Speicheradresse angibt. Diese Speicheradresse kann dann im myPerson gespeichert werden. Ein viel sinnvoller Aufruf sähe demnach also so aus:
myPerson = [Person alloc];
Bevor man dieses Objekt aber wirklich benutzen kann, ist es nötig, dass es initialisiert wird. Das erledigt die init-Methode.
[myPerson init];
Um sich etwas Schreibarbeit zu sparen, ist es möglich, diese Befehle zusammenzufassen, so das man mit zwei Zeilen auskommt.
Person *myPerson = [[Person alloc] init];
Wie Sie sicher gemerkt haben, findet sich eine identische Programmzeile auch im Programm des letzten Kapitels.

Warum aber die Trennung von Deklaration einer Variablen und der Definition, dem tatsächlichen Anlegen von Speicher? Dafür gibt es verschiedene Gründe:
Will man eine Variable für alle Methoden einer Klasse verfügbar machen, so muss diese global, das bedeutet ausserhalb einer Methode deklariert werden. Definieren kann man eine Variable aber dort nicht, das muss weiterhin in einer Methode geschehen.
Es gibt aber noch einen weiteren Grund Deklaration und Definition zu trennen. Unter Umständen möchte man für eine Variable gar keinen eigenen Speicher anfordern, sondern die Variable soll den Zeiger eines schon existierenden Objektes aufnehmen. Das zwei Variablen auf das selbe Objekt zeigen, ist nicht ungewöhnlich, denken Sie nur an das Programm aus dem Beispiel NSMutableString.

Wollen Sie aber eine neues Objekt erzeugen, sind die Schritte Deklaration, Definition und Initialisierung.

Objekte, die auf diese Art erzeugt werden, haben allerdings die Eigenschaft, dass sie so lange im Speicher bleiben, bis man sie wieder entfernt. Um den Speicherbereich eines Objektes wieder freizugeben gibt es die release-Methode.
[myPerson release];
Allerdings ist die oben getätigte Ausgabe nicht ganz korrekt. Will man wirklich ein Objekt manuell aus dem Speicher entfernen, geht das mit dealloc Aufruf.
[myPerson dealloc];
Release tut eigentlich etwas Anderes, aber dafür ist etwas Erklärung über das Speichermanagment nötig.

In Objective-C hat jedes Objekt einen retain-Zähler. Wird ein Objekt mit alloc erzeugt erhält der Zähler den Wert 1. Der Aufruf von release bewirkt, das der Zähler um den Wert 1 subtrahiert wird. Sobald der Zähler den Wert 0 erreicht, wird das Objekt aus dem Speicher entfernt. Interessant wird dieses Konzept erst dann, wenn mehrere Programmteile Zugriff auf das gleiche Objekt haben. Oder wenn Sie Objekte an Methoden übergeben. Da die linke Hand oft nicht weiss was die rechte tut, kann kein Programmteil ein Objekt aus dem Speicher entfernen, denn er hat keine Informationen darüber, ob das Objekt an anderer Stelle noch gebraucht wird. In so einem Fall ist es nötig, den retain-Zähler manuell zu erhöhen.

Der Aufruf von retain erhöht den retain-Zähler, während release ihn wieder reduziert. Erreicht der Zähler den Wert 0, dass heißt alle Methoden haben das Objekt freigegeben, wird es aus dem Speicher entfernt. Natürlich kann man es durch unglückliche Programmierung trotzdem schaffen, auf ein Objekt zuzugreifen, das nicht mehr existent ist, aber im Normalfall sollte so etwas nicht passieren.

In Ihrer Personenklasse haben Sie folgende Methode.
-(void) setName:(NSString *)aName
{
    [aName retain];
    [name release];
    name = aName;
}
Zuerst wird ein retain an aName geschickt, dass bedeutet, dieser Wert wird ‚festgehalten‘. Ein release an name gibt den Wert frei, der möglicherweise bisher in dieser Variablen gespeichert werden. Dann wird name gleich aName gesetzt. Anders als bei einfachen Datentypen wird hier keine Kopie erzeugt, sondern nur der Speicherzeiger übergeben.

Speicherbereiche für benutzte Objekte nicht wieder freizugeben, ist ein typischer Programmierfehler,der leider immer wieder auftritt. Diese sogenannten Memory-Leaks sind unter Umständen gar nicht schlimm und fallen bei normaler Nutzung einer Programms nicht auf, es ist aber auch möglich, das sich ein Programm in wenigen Minuten einen gigantischen Speicherbereich reserviert und damit ganze Systeme blockiert.

Seit Objective-C Version 2, das mit Xcode 3 ausgeliefert wurde, besitzt diese Programmiersprache nun auch einen Garbage Collector. Dies ist ein Mechanismus, der in Intervallen nicht mehr benutzte Objekte erkennt und aus dem Speicher entfernt. Natürlich stellt dies ein großer Fortschritt dar, und erleichtert sehr die Erstellung robuster Anwendungen. Denken Sie aber daran, dass Programme welche den Garbage Collector verwenden, nicht in Betriebssysteme unterhalb Version 10.5 lauffähig sind. Auch wenn sie mit Objective-C für das iPhone entwickeln wollen, haben Sie dort keinen Garbage Collector. Es ist also immer eine gute Idee, die Speicherverwaltung selbst zu übernehmen oder zumindest zu wissen, wie sie funktioniert. Natürlich ist die Verwendung einer automatischen Speicherverwaltung vollkommen legitim, aber da Sie wenig Kontrolle über der Garbage Collector haben, kann es passieren, dass er zu keinen Hängern in ihrem Programm führt. Das aber auch nur, wenn Sie wirklich viele Daten haben, wie zum Beispiel in der Videobearbeitung.

Den Garbage Collector können Sie für Ihr Projekt unter den Target-Eigenschaften festlegen. Insgesamt gibt es drei verschiedene Möglichkeiten:
stacks_image_B4757BA0-F104-4A76-8CC0-F537FE2B96E9
Unsupported: Dies ist die Standardeinstellung und bedeutet, dass Sie die Speicherverwaltung selbst übernehmen müssen.

Supported -fobjc-gc: Garbage Collector und manuelle retain/release Programmlogik ist möglich.

Required -fobjc-gc-only: Der Garbage Collector übernimmt die komplette Speicherverwaltung.

In der Regel wird der Garbage Collector automatisch aktiv, wenn die Speichernutzung einen bestimmen Wert überschreitet. Trotzdem ist es möglich den Garbage Collector manuell zu starten. Die kann zum Beispiel sinnvoll werden wenn in einer Schleife sehr viele Objekte erzeugt wurden.
NSGarbageCollector *myCollector = [NSGarbageCollector defaultCollector];
[myCollector collectIfNeeded];