Kontrollfluss in Swift
Mit if und else entscheidet dein Programm, mit guard steigt es früh aus, wenn eine Bedingung nicht erfüllt ist. switch vergleicht einen Wert mit Mustern und muss alle möglichen Fälle abdecken. Wiederholungen schreibst du meist mit for-in über einen Bereich oder eine Collection, seltener mit while.
Danach kannst du
- schreibst Bedingungen mit if, else if und guard
- nutzt switch mit Bereichen, Tupeln und where-Bedingungen
- wählst zwischen for-in, while und repeat-while
- verstehst, warum switch vollständig sein muss
Voraussetzungen
Umgebung
Swift 6.4.0, Sprachmodus 6
plattformunabhängig, geprüft unter Linux x86-64
Auf dieser Seite
if und else
if prüft eine Bedingung vom Typ Bool. Anders als in C braucht die Bedingung keine Klammern, die geschweiften Klammern um den Block sind dafür Pflicht, auch bei nur einer Zeile.
func bewerte(punkte: Int) -> String {
if punkte >= 90 {
return "sehr gut"
} else if punkte >= 50 {
return "bestanden"
} else {
return "noch nicht bestanden"
}
}
func anmelden(name: String) -> String {
guard !name.isEmpty else {
return "Bitte gib einen Namen ein."
}
// Ab hier ist sicher: name ist nicht leer
return "Willkommen, \(name)!"
}
print(bewerte(punkte: 95))
print(bewerte(punkte: 42))
print(anmelden(name: ""))
print(anmelden(name: "Ada"))
Ausgabe
sehr gut noch nicht bestanden Bitte gib einen Namen ein. Willkommen, Ada!
Die Bedingungen in bewerte werden von oben nach unten geprüft; der erste zutreffende Zweig gewinnt. Deshalb steht >= 90 vor >= 50. In umgekehrter Reihenfolge würde auch eine 95 nur „bestanden“ ergeben.
guard: früh aussteigen
guard ist ein umgedrehtes if: Der else-Block läuft, wenn die Bedingung nicht erfüllt ist, und er muss die Funktion verlassen, etwa mit return oder throw. Der Compiler prüft das. Nach dem guard weißt du deshalb sicher, dass die Bedingung gilt.
Der Vorteil zeigt sich in längeren Funktionen. Statt den eigentlichen Code immer tiefer in if-Blöcke einzurücken, prüfst du am Anfang alle Voraussetzungen und schreibst den Rest ohne Verschachtelung. In der Lektion zu Optionals siehst du guard let, die häufigste Form.
switch: vergleichen mit Mustern
switch vergleicht einen Wert mit mehreren Mustern. In Swift ist das viel mächtiger als in C oder Java: Ein Fall kann ein Bereich sein, ein Tupel, eine Bedingung mit where oder ein Muster, das Werte herauszieht.
func beschreibe(_ temperatur: Int) -> String {
switch temperatur {
case ..<0:
return "Frost"
case 0..<15:
return "kühl"
case 15...25:
return "angenehm"
default:
return "heiß"
}
}
for wert in [-3, 8, 21, 31] {
print(wert, beschreibe(wert))
}
func lage(_ punkt: (x: Int, y: Int)) -> String {
switch punkt {
case (0, 0):
return "Ursprung"
case (0, let y):
return "auf der y-Achse bei \(y)"
case let (x, y) where x == y:
return "auf der Diagonalen"
default:
return "irgendwo anders"
}
}
for punkt in [(0, 0), (0, 5), (3, 3), (2, 7)] {
print(punkt, lage(punkt))
}
Ausgabe
-3 Frost 8 kühl 21 angenehm 31 heiß (0, 0) Ursprung (0, 5) auf der y-Achse bei 5 (3, 3) auf der Diagonalen (2, 7) irgendwo anders
Drei Dinge unterscheiden Swifts switch von dem aus vielen anderen Sprachen:
- Kein Durchfallen. Nach einem passenden Fall ist Schluss, ein
breakbrauchst du nicht. Mehrere Werte für denselben Fall trennst du mit Komma:case 6, 7:. - Muster mit Bindung.
case (0, let y)passt auf jeden Punkt mit x gleich 0 und legt den y-Wert in einer neuen Konstante ab. - Vollständigkeit. Jeder mögliche Wert muss von einem Fall abgedeckt sein.
Der letzte Punkt sorgt am Anfang für Fehlermeldungen, ist aber eine der nützlichsten Regeln der Sprache:
enum Ampel {
case rot, gelb, gruen
}
func darfFahren(_ ampel: Ampel) -> Bool {
switch ampel {
case .gruen:
return true
case .rot:
return false
}
}
Compiler-Meldung
switch-unvollstaendig.swift:6:5: error: switch must be exhaustive
4 |
5 | func darfFahren(_ ampel: Ampel) -> Bool {
6 | switch ampel {
| |- error: switch must be exhaustive
| `- note: add missing case: '.gelb'
7 | case .gruen:
8 | return trueDer Compiler nennt den fehlenden Fall gleich mit. Bei Int oder String gibt es unendlich viele mögliche Werte, dort brauchst du ein default. Bei einem Enum wie Ampel dagegen kann der Compiler alle Fälle aufzählen. Wenn du hier auf default verzichtest und dem Enum später einen Fall hinzufügst, zeigt dir der Compiler jede Stelle, die du anpassen musst. Mehr dazu in der Lektion zu Enums.
Schleifen
Die häufigste Schleife ist for-in. Sie läuft über einen Bereich, eine Collection oder alles, was das Protokoll Sequence erfüllt.
// Bereich: 1 bis einschließlich 3
for runde in 1...3 {
print("Runde \(runde)")
}
// Schrittweite mit stride: 0, 25, 50, 75, 100
let marken = stride(from: 0, through: 100, by: 25).map { "\($0) %" }
print(marken.joined(separator: " | "))
// Index und Wert gemeinsam
for (index, name) in ["Ada", "Grace", "Linus"].enumerated() {
print(index, name)
}
// while prüft vorher, repeat-while prüft nachher
var kontostand = 100
var monate = 0
while kontostand > 0 {
kontostand -= 30
monate += 1
}
print("Nach \(monate) Monaten: \(kontostand) €")
var versuche = 0
repeat {
versuche += 1
} while versuche < 0
print("repeat lief \(versuche)-mal")
Ausgabe
Runde 1 Runde 2 Runde 3 0 % | 25 % | 50 % | 75 % | 100 % 0 Ada 1 Grace 2 Linus Nach 4 Monaten: -20 € repeat lief 1-mal
for runde in 1...3zählt einen Bereich ab. Brauchst du die Zahl nicht, schreibst dufor _ in 1...3.stride(from:through:by:)erzeugt Werte mit fester Schrittweite.throughschließt das Ende ein,tonicht.enumerated()liefert Paare aus laufender Nummer und Element.whileprüft die Bedingung vor jedem Durchlauf und läuft unter Umständen gar nicht.repeat-whileprüft danach und läuft deshalb mindestens einmal, wie die Ausgabe zeigt, obwohlversuche < 0nie erfüllt ist.
Achte auf die Kontostand-Schleife: Sie stoppt bei −20 €, nicht bei 0 €, weil sie erst nach dem Abziehen prüft. Solche Grenzfälle sind die häufigste Fehlerquelle in Schleifen. Probiere aus, was passiert, wenn du die Bedingung auf kontostand >= 30 änderst.
Mit break verlässt du eine Schleife vorzeitig, mit continue springst du zum nächsten Durchlauf. In vielen Fällen sind aber Funktionen wie filter, map oder first(where:) klarer; die lernst du bei den Collections und Closures.
Häufige Fehler
switch must be exhaustive. Ein Fall fehlt. Lies die note: Sie nennt den fehlenden Fall. Füge ihn hinzu oder, bei offenen Wertebereichen, ein default.
Ein Zweig wird nie erreicht. Die Reihenfolge der Bedingungen stimmt nicht; ein weiter gefasster Fall steht vor einem engeren. Bei if-Ketten und bei switch über Zahlenbereiche gewinnt immer der erste passende Fall, der Compiler meldet Überschneidungen dort nicht.
Endlosschleife mit while. Die Bedingung wird im Schleifenkörper nie falsch. Prüfe, ob sich die Variable aus der Bedingung in jedem Durchlauf ändert.
Übungen
- Schreibe eine Funktion
fizzbuzz(_ n: Int) -> String, die für Vielfache von 15 „FizzBuzz“, von 3 „Fizz“, von 5 „Buzz“ und sonst die Zahl als Text liefert. Gib das Ergebnis für 1 bis 15 aus. Nutzeswitchmit einem Tupel aus zweiBool-Werten. - Ändere die Kontostand-Schleife so, dass sie nie ins Minus geht und am Ende ausgibt, wie viel übrig bleibt.
Lösung zu Übung 1
func fizzbuzz(_ n: Int) -> String {
switch (n % 3 == 0, n % 5 == 0) {
case (true, true):
return "FizzBuzz"
case (true, false):
return "Fizz"
case (false, true):
return "Buzz"
case (false, false):
return String(n)
}
}
print((1...15).map(fizzbuzz).joined(separator: " "))
Ausgabe
1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz
Das Tupel (n % 3 == 0, n % 5 == 0) hat genau vier mögliche Werte, und alle vier sind abgedeckt. Deshalb braucht dieses switch kein default.
Lösung zu Übung 2
var kontostand = 100
var monate = 0
let rate = 30
while kontostand >= rate {
kontostand -= rate
monate += 1
}
print("Nach \(monate) Monaten bleiben \(kontostand) €")
Ausgabe
Nach 3 Monaten bleiben 10 €