Moderne Programmierung mit Swift, Teil 1

Knapp drei Jahre nach der ersten Vorstellung auf Apples Entwicklerkonferenz WWDC 2014 hat die Programmiersprache Swift bereits zwei Major Releases zu verzeichnen. Eine dreiteilige Artikelserie widmet sich dem Status quo und stellt gleichermaßen grundlegende wie neu eingeführte Konzepte vor.

In Pocket speichern vorlesen Druckansicht 16 Kommentare lesen
Moderne Programmierung mit Swift, Teil 1
Lesezeit: 21 Min.
Von
  • Thomas Sillmann
  • Julia Schmidt
Inhaltsverzeichnis

Swift hat in den letzten Jahren einen starken Wandel durchlebt, der erst in jüngster Zeit langsam abflaut. Einige Konzepte wurden gestrichen und dafür komplett neue hinzugefügt. Apple und die Open-Source-Community von Swift sind aktuell primär damit beschäftigt, die Basis der Sprache sowie Typen und Funktionen der Standard Library zu vereinheitlichen und zu optimieren, was sich insbesondere in der für Herbst erwarteten Version 4 der Sprache niederschlagen wird.

Um Einsteigern und mit Swift programmierenden Entwicklern einen Überblick über zentrale und neue Konzepte der Sprache an die Hand zu geben, stellt eine dreiteilige Artikelserie nach und nach Elemente wie Error Handling, Initialisierung, Operator Methods und das Zusammenspiel zwischen Swift und Objective-C im Detail vor. Den Anfang machen Error Handling, die Speicherverwaltung in Swift und Option Sets.

Error Handling gehörte zu den spannendsten Features, die Swift mit der 2015 freigegebenen Version 2 zuteil wurden. Es stellt eine Alternative zum Einsatz der NSError-Klasse aus dem Foundation Kit dar, die in Objective-C heute noch dazu dient, Fehler abzufangen und auf sie zu reagieren. Da Error Handling darüber hinaus fester Bestandteil der Sprache ist, ist dessen Einsatz nicht wie NSError auf Apple-Plattformen beschränkt und lässt sich unter Linux und auf dem Server verwenden.

Basis des Error Handling in Swift ist das Schlüsselwort throws. Entwickler können Methoden damit versehen, um anzugeben, dass sie möglicherweise fehlschlagen und einen Fehler zurückgeben können. throws wird dabei immer hinter den runden Klammern gesetzt, die die Parameter einer Methode enthalten. Damit steht das Schlüsselwort immer vor den geschweiften Klammern, die die Implementierung einer Methode enthalten. Bei Methoden mit einem Rückgabewert ist es vor der Angabe des entsprechenden Rückgabetyps zu finden:

func functionWithThrowingError() throws {
// Implementierung der Funktion
}

func functionWithThrowingErrorAndReturnValue() throws -> Int {
// Implementierung der Funktion
}

Nur wenn eine Methode mit dem Schlüsselwort throws deklariert ist, ist sie auch imstande, einen Fehler zurückzugeben. Grundlage für dessen Definition ist das Protokoll Error. Fehler, die in einer Anwendung entstehen können, sind mithilfe eines protokollkonformen Typs abzubilden. Typischerweise setzt man zu dem Zweck Enumerations ein und definiert für jeden potenziellen Fehler einen eigenen Case. Sollte einer zur Beschreibung nicht ausreichen, lässt sich optional noch mit Associated Values arbeiten, die einem Case zugeordnet sind.

Ein Beispiel für die Abbildung von Fehlern zeigt der folgende Quelltextauszug. Darin wird eine Enumeration DrivingError erstellt, die zwei Fehlerfälle festlegt: einen, falls für ein Fahrzeug kein Treibstoff zur Verfügung steht, und einen für den Fall, dass die Geschwindigkeit des Fahrzeugs zu hoch ist. Der letzte Case verfügt zusätzlich über einen Associated Value, der über die Höchstgeschwindigkeit eines Fahrzeugs informiert.

enum DrivingError: Error {
case noFuel
case speedTooHigh(maximumSpeed: UInt)
}

Innerhalb einer Methode wird ein Fehler mithilfe des Schlüsselworts throw zurückgegeben, wobei direkt nach letzterem der entsprechende Fehler aus einem zum Error-Protokoll konformen Typ angegeben wird. Dabei gilt, dass throw nur dann innerhalb einer Methode gefeuert werden darf, wenn die Methode selbst durch das Schlüsselwort deklariert wurde.

Der folgende Quellcodeauszug zeigt ein Beispiel für das Feuern von Fehlern mit dem Error-Handling-Modell von Swift:

struct Car {
var hasFuel: Bool
let maximumSpeed: UInt
var currentSpeed: UInt

func startDriving() throws {
guard hasFuel else {
throw DrivingError.noFuel
}

print("Start driving...")
}

mutating func increaseCurrentSpeedWithSpeed(_ speed: UInt) throws -> UInt {

let updatedSpeed = currentSpeed + speed
guard updatedSpeed <= maximumSpeed else {

throw DrivingError.speedTooHigh(maximumSpeed: maximumSpeed)
}

currentSpeed = updatedSpeed
print("Current speed is now \(currentSpeed).")
return currentSpeed
}
}

Das Beispiel deklariert eine Structure Car, die über insgesamt drei Properties verfügt:

  • hasFuel gibt an, ob die Instanz über genügend Treibstoff zum Fahren verfügt,
  • maximumSpeed gibt die zu erreichende Höchstgeschwindigkeit des Autos an und
  • currentSpeed bezieht sich auf die aktuelle Geschwindigkeit eines Fahrzeugs.

Darüber hinaus kommen noch zwei Methoden zum Einsatz, die beide via throws deklariert sind und so unter bestimmten Umständen wenigstens einen Fehler zurückgeben können. Die Methode startDriving() prüft den Wert der Eigenschaft hasFuel. Ist er false, wird der Fehler noFuel der DrivingError-Enumeration zurückgegeben und die Methode verlassen, andernfalls gibt das Programm eine Info auf der Konsole aus, die besagt, dass das Auto losfährt. Die zweite Methode, increaseCurrentSpeedWithSpeed(_:) erhöht die aktuelle Geschwindigkeit um den übergebenen Wert und prüft anschließend, ob damit möglicherweise die Maximalgeschwindigkeit des Fahrzeugs überschritten ist. Falls ja, gibt das Programm den Fehler speedTooHigh mitsamt der entsprechenden Höchstgeschwindigkeit zurück. Andernfalls wird der Wert der Property currentSpeed geändert und zusätzlich eine Meldung auf der Konsole ausgegeben. Anschließend liefert die Structure die neue aktuelle Geschwindigkeit zurück.