Moderne Programmierung mit Swift, Teil 2

Seite 4: Typen prüfen und umwandeln

Inhaltsverzeichnis

Type Checking und Type Casting bezeichnen Techniken, um eine Variable oder Konstante mit einem bestimmten Typ zu vergleichen und so spezifischere Eigenschaften und Funktionen – sofern verfügbar – abzurufen. Zur Erläuterung der beiden Vorgänge soll das folgende Beispiel dienen. Darin werden insgesamt drei Klassen deklariert: Story, Book und Audiobook. Story dient zur Definition einer Geschichte und verfügt dazu über die beiden Properties title und author.

Book und Audiobook sind Subklassen von Story und bringen zusätzlich weitere spezifische Informationen mit. So enthält Erstere die Anzahl der Seiten eines Buches, und Zweitere definiert seine Länge und von wem es gelesen wird. Im Anschluss erstellt das Programm zwei Instanzen des Typs Book und eine vom Typ Audiobook und weist sie einem Array namens stories zu.

class Story {
var author: String
var title: String
init(author: String, title: String) {
self.author = author
self.title = title
}
}

class Book: Story {
var numberOfPages: UInt

init(author: String, title: String, numberOfPages: UInt) {
self.numberOfPages = numberOfPages
super.init(author: author, title: title)
}
}

class Audiobook: Story {
var reader: String
var durationInHours: UInt
init(author: String, title: String, reader: String, durationInHours: UInt) {
self.reader = reader
self.durationInHours = durationInHours
super.init(author: author, title: title)
}
}


let it = Book(author: "Stephen King", title: "Es", numberOfPages: 1536)
let theStand = Book(author: "Stephen King", title: "The Stand - Das letzte ↲
Gefecht", numberOfPages: 17
let theBundle = Audiobook(author: "Sebastian Fitzek", title: "Das Paket", ↲
reader: "Simon Jäger", durationInHours: 7)
let stories = [it, theStand, theBundle]

Beim Einsatz des Array stories ergibt sich nun allerdings ein Problem: Swift definiert es durch Type Inference als Konstante vom Typ [Story], weil die Klasse Story dem kleinsten gemeinsamen Nenner der Elemente des Arrays entspricht. Liest man jene Elemente nun beispielsweise mit einer for-in-Schleife aus, lassen sich nur jene Eigenschaften und Funktionen nutzen, die in der Klasse Story definiert sind. Die spezifischeren Eigenschaften und Funktionen der Typen Book und Audiobook kann man so nicht ohne Weiteres verwenden.

An der Stelle kommen Type Checking beziehungsweise Type Casting ins Spiel. Mithilfe des Type Checking lässt sich zunächst einmal prüfen, ob eine Variable beziehungsweise Konstante einem bestimmten Typ entspricht. Das Element it des stories-Arrays beispielsweise liest das Programm zwar als Element vom Typ Story aus dem Array aus, es entspricht in Wirklichkeit aber der Subklasse Book, während das Array-Element theBundle konform zu Audiobook ist.

Der folgende Codeauszug zeigt, wie die Elemente des stories-Array via Type Checking mit den beiden spezifischeren Subklassen verglichen werden und je nach Ergebnis eine angepasste print()-Meldung ausgegeben wird. Dazu liest das Programm zunächst alle Elemente des Arrays mit einer for-in-Schleife aus und vergleicht sie anschließend durch Type Checking mit Book beziehungsweise Audiobook. Dabei kommt das Schlüsselwort is zum Einsatz.

for story in stories {
if story is Book {
print("'\(story.title)' von \(story.author) ist ein Buch.")
} else if story is Audiobook {
print("'\(story.title)' von \(story.author) ist ein Hörbuch.")
}
}
// 'Es' von Stephen King ist ein Buch.
// 'The Stand - Das letzte Gefecht' von Stephen King ist ein Buch.
// 'Das Paket' von Sebastian Fitzek ist ein Hörbuch.

Mithilfe des Type Checking lässt sich so prüfen, ob eine Variable beziehungsweise Konstante einem bestimmten Typ entspricht. Die Elemente des stories-Array entsprechen allesamt einem spezifischeren Typ als Story, doch da die Klasse Story die gemeinsame Obermenge darstellt, ist ein Casting notwendig, um auf die spezifischeren Eigenschaften und Funktionen der Elemente zugreifen zu können.

Ein solches Type Casting lässt sich in Swift ganz ähnlich wie das eben beschriebene Type Checking ausführen, statt is kommt allerdings das Schlüsselwort as zum Einsatz. Damit können Entwickler angeben, dass eine Variable oder Konstante konform zum angegebenen Typ ist.

Es gibt zwei Varianten von as: Mit as? (man beachte das Fragezeichen) erhält man ein Optional, das entweder nil – sollte eine Casting nicht möglich sein – oder der erfolgreich umgewandelten Variablen oder Konstanten entspricht. as! (mit Ausrufezeichen) hingegen führt das Casting in jedem Fall durch. Dabei ist aber Vorsicht geboten: Wird eine Variable beziehungsweise Konstante so in einen Typ überführt, dem sie nicht entspricht, zieht das einen Absturz der Anwendung nach sich. Idealerweise prüft man mit Type Checking vorher, ob ein Casting erfolgreich sein kann.

Das folgende Beispiel zeigt die beschriebene Technik mittels as! und den dadurch möglichen Zugriff auf die speziellen Eigenschaften und Funktionen der Story-Subklassen Book und Audiobook.

for story in stories {
if story is Book {
let book = story as! Book
print("'\(book.title)' von \(book.author) ist ein Buch mit ↲
\(book.numberOfPages) Seiten.")
} else if story is Audiobook {
let audiobook = story as! Audiobook
print("'\(audiobook.title)' von \(audiobook.author) ist ein Hörbuch mit ↲
einer Spieldauer von \(audiobook.durationInHours) Stunden.")
}
}

// 'Es' von Stephen King ist ein Buch mit 1536 Seiten.
// 'The Stand - Das letzte Gefecht' von Stephen King ist ein Buch
// mit 1712 Seiten.
// 'Das Paket' von Sebastian Fitzek ist ein Hörbuch mit einer Spieldauer
// von 7 Stunden.

Alternativ dazu zeigt das folgende Beispiel das Type Casting durch Optional Binding und der Verwendung von as?. Das Ergebnis ist identisch zu dem des vorangegangenen Codes.

for story in stories {
if let book = story as? Book {
print("'\(book.title)' von \(book.author) ↲
ist ein Buch mit \(book.numberOfPages) Seiten.")
} else if let audiobook = story as? Audiobook {
print("'\(audiobook.title)' von \(audiobook.author) ist ein Hörbuch ↲
mit einer Spieldauer von \(audiobook.durationInHours) Stunden.")
}
}