Moderne Programmierung mit Swift, Teil 1

Verwaltung des Speichers

Swift nimmt Entwicklern einen Großteil der Arbeit ab, wenn es um die Speicherverwaltung geht. Per ARC (Automatic Reference Counting) werden – genau wie in Objective-C – die Referenzen auf eine Instanz im Speicher gezählt. Ist die Zahl größer 0, heißt das, dass die Instanz noch benötigt und somit auch im Speicher gehalten wird. Entspricht sie hingegen 0, ist das das Zeichen dafür, dass das Programm die Instanz nicht länger benötigt und aus dem Speicher entfernen kann.

Allerdings kann es bei der Technik zu sogenannten Strong Reference Cycles kommen. Sie entstehen, wenn zwei Instanzen sich gegenseitig referenzieren, während das Programm die Variablen freigibt, die je einen Verweis auf jene Instanzen besitzen. Dadurch gibt es keinen Weg mehr, um auf die beiden im Speicher befindlichen Instanzen zuzugreifen, und die Instanzen bleiben ewig im Speicher.

Zur Verdeutlichung soll der folgende Quellcodeauszug dienen. Dort werden zunächst die Klassen Driver und Vehicle deklariert. Sie besitzen beide je eine Property des jeweils anderen Typs. So lässt sich einem Fahrer ein passendes Fahrzeug zuweisen und einem Fahrzeug ein Fahrer. Anschließend werden zwei Variablen erzeugt – aDriver und aVehicle – und ihnen je eine neu erstellte Instanz der beiden Klassen zugewiesen. Der Referenzzähler für die beiden neuen Instanzen steht an der Stelle somit auf 1. Im nächsten Schritt werden jene Instanzen den Properties driver und vehicle der erstellten Variablen zugeordnet, wodurch sie auch auf die Instanzen verweisen und somit deren Referenzzähler auf 2 steigt. Abschließend gibt das Programm die Variablen durch Zuweisung von nil frei. Sie verweisen also nicht länger auf die Driver- und Vehicle-Instanzen, womit deren Referenzzähler um 1 verringert wird.

class Driver {
var vehicle: Vehicle?
}

class Vehicle {
var driver: Driver?
}

var aDriver: Driver? = Driver()
var aVehicle: Vehicle? = Vehicle()

// Referenzzähler der erzeugten Driver-Instanz: 1

// Referenzzähler der erzeugten Vehicle-Instanz: 1

aVehicle?.driver = aDriver

aDriver?.vehicle = aVehicle

// Referenzzähler der Driver-Instanz: 2
// Referenzzähler der Vehicle-Instanz: 2

aDriver = nil
aVehicle = nil

// Referenzzähler der Driver-Instanz: 1
// Referenzzähler der Vehicle-Instanz: 1

Zirkuläre Abhängigkeiten vermeiden

Das Problem besteht nun darin, dass aus dem Code heraus kein Verweis mehr auf die Driver- und Vehicle-Instanz existiert, sie aber noch immer – aufgrund ihrer gegenseitigen Referenzierung – einen Referenzzähler größer 0 besitzen. Folglich bleiben sie im Speicher und lassen sich nicht mehr daraus entfernen; ein Strong Reference Cycle ist entstanden.

Entwickler sind dafür verantwortlich, das Potenzial derartiger Konstrukte zu erkennen und entsprechend darauf zu reagieren. Abhilfe schafft das Schlüsselwort weak, mit dem sich in Swift Properties deklarieren lassen. Wird einer solchen Weak Property eine Instanz zugewiesen, erhöht sich deren Referenzzähler nicht. Sie verweisen lediglich auf eine Instanz im Speicher, während die Strong Properties (die den Standard in Swift darstellen) eine zusätzliche Instanz im Speicher halten und entsprechend deren Referenzzähler erhöhen.

Um nun das beschriebene Problem eines Strong Reference Cycle zu lösen, ist bei sich gegenseitig referenzierenden Properties (wie im Falle der Properties der Klassen Driver und Vehicle) eine der Properties als Weak Property zu deklarieren. Das sorgt dafür, dass die starke Referenzierung zwischen Instanzen der jeweiligen Klassen aufgehoben wird. Der folgende Code zeigt beispielhaft die Deklaration der Property driver der Klasse Vehicle als Weak-Property.

class Vehicle {          
weak var driver: Driver?
}

Wie sich diese kleine Änderung auf das Beispiel von eben auswirkt, ist im folgenden Quellcodeauszug zu sehen. Dort werden erneut Instanzen der Klassen Driver und Vehicle erstellt und deren jeweiligen Properties jene Instanzen zugewiesen. An der Stelle tritt bereits der erste Unterschied auf. Während der Referenzzähler der Vehicle-Instanz um 1 erhöht wird, sobald sie der vehicle-Property der Driver-Instanz zugewiesen wird, ist das umgekehrt bei der Driver-Instanz nicht der Fall. Da sie mit einer Weak Property versehen ist, bleibt der Referenzzähler unverändert auf 1. Sobald das Programm der Variablen aDriver dann nil zuweist, verringert sich der Stand des Referenzzählers der Instanz auf 0 und sie verschwindet aus dem Speicher. Der Zähler der Vehicle-Instanz sinkt im gleichen Zuge um 1, schließlich verweist die vehicle-Property der eben aufgelösten Driver-Instanz nicht mehr auf das Objekt. Sobald aVehicle ebenfalls nil zugewiesen wird, entfernt das Programm die verbliebene Instanz aus dem Speicher und hebt so den Strong Reference Cycle auf.

var aDriver: Driver? = Driver()
var aVehicle: Vehicle? = Vehicle()

// Referenzzähler der erzeugten Driver-Instanz: 1
// Referenzzähler der erzeugten Vehicle-Instanz: 1

aVehicle?.driver = aDriver
aDriver?.vehicle = aVehicle

// Referenzzähler der Driver-Instanz: 1
// Referenzzähler der Vehicle-Instanz: 2

aDriver = nil

// Referenzzähler der Driver-Instanz: 0 (Instanz wird aus Speicher entfernt)
// Referenzzähler der Vehicle-Instanz: 1

aVehicle = nil

// Referenzzähler der Vehicle-Instanz: 0 (Instanz wird aus Speicher
// entfernt)