Einführung in Apples neue Programmiersprache Swift, Teil 2

Seite 3: Protokolle, Compiler

Inhaltsverzeichnis

Klassen oder Strukturen sind nicht die einzigen Konstrukte zur Definition oder Erweiterung von Datentypen. Mit dem Schlüsselwort protocol definieren Entwickler Protokolle, die sich von Klassen, Verbundtypen und sogar Aufzählungstypen implementieren lassen. Zudem ist es möglich, Typen mit Extensions zu erweitern – ein Mechanismus, den Sprachdesigner im Allgemeinen als Mixin bezeichnen. Aber nun eines nach dem anderen.

Protokolle lassen sich grob mit Java-Schnittstellen oder Scala-Traits vergleichen. Ein Protokoll repräsentiert eine Rolle. Es schreibt zum Beispiel Methoden und Eigenschaften vor, die Klassen oder Verbundtypen implementieren müssen, wollen sie die Rolle unterstützen. Das Protokoll selbst enthält keine Implementierung, sondern dient lediglich als Blaupause. Klassen oder Strukturen können ein oder mehrere Protokolle unterstützen, indem sie in ihrer Klassendefinition die jeweiligen Protokolle spezifizieren und die entsprechenden Methoden und Eigenschaften dieser Protokolle implementieren. Nach außen präsentiert sich ein Protokoll als ein Referenz-Datentyp und lässt sich auch so nutzen. Hier ein einfaches Beispiel mit dem Protokoll IstOnlineVerfügbar:

protocol IstOnlineVerfügbar {
func url() -> String // einziges Element des Protokolls
}

class HeiseZeitschrift : Zeitschrift, IstOnlineVerfügbar {
/* ... einige Details ausgelassen ... */
var link : String
func url() -> String { // hier ist die Implementierung des Protokolls
return link
}
init(titel : String, ... , url : String) {
/* selbstredend nur getesteter Code ? */
}
}

Hat eine Klasse sowohl eine Elternklasse als auch Protokolle wie die Klasse HeiseZeitschrift, ist die Elternklasse in der Kindklasse als Erstes zu spezifizieren, gefolgt durch die mit Komma getrennten Protokolle. Die Initialisierung der Eigenschaft link im Beispiel erfolgt innerhalb der init-Methode. Anschließend lässt sich das Protokoll wie folgt als Datentyp nutzen:

var iX = HeiseZeitschrift("iX",....)
var online : IstOnlineVerfügbar = iX
println(online.url) // => http://www.heise.de/ix/

Was schenkt man jemand, der schon alles hat? Erweiterungen! Als extension bezeichnet Swift Erweiterungen, die Entwickler einem Datentyp nachträglich hinzufügen können. Andere Programmiersprachen wie Ruby oder Scala offerieren solche Mixins ebenfalls. Extensions sind auch für Grundtypen wie Int und String anwendbar und können das ganze Spektrum von Vereinbarungen inkludieren, zum Beispiel auch Initialisierungsfunktionen, Eigenschaften oder Methoden. Hier ein einfaches Beispiel mit einer (zugegeben überflüssigen) Erweiterung für Ganzzahlen:

extension : Int {
func addMagicNumber() {
self = self + 42
}
}

0.addMagicNumber() // liefert 42

Wie das Beispiel zeigt, "fühlen" sich die Erweiterungen für den Nutzer so an, als wären sie inhärenter Bestandteil des erweiterten Datentyps.

Sowohl der Swift- als auch der Objective-C-Compiler erzeugen als Zwischenschritt LLVM-Bytecode, der sich danach effizient auf verschiedene Hardwarearchitekturen abbilden lässt – derzeit allerdings im Falle von Swift nur auf Systeme mit iOS oder OS X als Betriebssystem. Obwohl es der Name suggeriert (LLVM hieß ursprünglich Low Level Virtual Machine) spezifiziert LLVM keine virtuelle Maschine, sondern einen Compiler, der aus Bytecode nativen Code für die Ausführungsplattform generiert. Bytecode ist Code für eine theoretische Hardware, die sich möglichst nah an verbreitete Hardwarearchitekturen anlehnt. Compilerbauer können sich daher im Wesentlichen auf das Erstellen des Frontends beschränken, während sich LLVM um das Backend kümmert. Wie kommt jetzt Bytecode ins Spiel? Das Frontend ist dafür verantwortlich, aus Swift-Dateien Bytecode zu erzeugen und das Ergebnis an LLVM zu übergeben. Swift liefert also ausführbare Programme und keine virtuelle Maschine, die Bytecode interpretiert.

Objective-C und Swift teilen sich neben LLVM sogar das gleiche Laufzeitsystem, was die Interoperabilität zwischen Kompilaten beider Sprachen deutlich vereinfacht. LLVM ist Open-Source-Software, genauso wie der von Apple benutzte Clang-Compiler (einer Alternative zu GCC), was die ganze Sache auch für Nicht-Apple-Entwickler spannend machen könnte, aber das ist spekulativ.

Die Speicherbereinigung in der Laufzeitumgebung von Swift erfolgt durch automatisches Referenzzählen (ARC = Automated Reference Counting). Zu jedem Objekt im Stapelspeicher gibt es einen Referenzzähler, den die Laufzeitumgebung automatisch initialisiert und aktualisiert. Das macht das Programmieren ein ganzes Stück sicherer, aber ganz ohne Mitdenken funktioniert es dennoch nicht. Wenn zwei Objekte gegenseitig aufeinander verweisen, blockieren sie ihre Vernichtung. Um solche Probleme zu vermeiden, gibt es in Swift zwei Möglichkeiten. Entweder definieren Entwickler die entsprechende Referenz in einer Richtung als weak (schwach). Auf schwache Referenzen muss das Laufzeitsystem nicht achten. Dadurch wird quasi eine Richtung des Zyklus durchbrochen. Oder sie markiert eine Referenz mit dem Schlüsselwort unowned – für diese Referenz wird der Referenzzähler erst gar nicht inkrementiert.

Es gibt noch viele weitere Aspekte von Swift zu entdecken. Etwa Delegates – die Weiterleitung von Verantwortlichkeiten an ein Delegate –, Subscripts – Array-Notation für eigene Collection-Datentypen –, der Schutz von Elternklassenmethoden vor dem Überschreiben in abgeleiteten Klassen, Typkonvertierungen, verschachtelte Typdefinitionen und noch einiges andere mehr.

Wer also Swift näher kennen lernen möchte, sollte wissen, dass Mitglieder des (kostenpflichtigen) Apple-Developer-Netzwerks schon jetzt auf eine frühe Version von Swift als Bestandteil von Xcode 6 Zugriff haben, ebenso auf Beta-Versionen von iOS 8 und OS X Yosemite. Die Mitgliedschaft kostet jährlich 99 US-Dollar. Es ist davon auszugehen, dass die produktreife Implementierung der Sprache zeitgleich mit den neuen Betriebssystemversionen im Herbst erscheinen dürfte.