Hintergrund:
defer wurde in Swift eingeführt, um eine bequemere Verwaltung von Ressourcen und das Ausführen von Operationen nach Abschluss eines Codeblocks zu ermöglichen, ähnlich wie finally in anderen Programmiersprachen. Diese Konstruktion hilft, die Phasen der Bereinigung oder des Abschlusses der Arbeit mit Ressourcen ausdrücklich darzustellen.
Problem:
Viele Anfänger glauben, dass defer sofort den verschachtelten Code ausführt, wobei der Zeitpunkt der Ausführung oft missverstanden wird. Es gibt auch Situationen, in denen mehrere defer-Blöcke schwer zu verstehen sind, wenn man die LIFO-Regel (last-in, first-out) nicht kennt. Es können Schwierigkeiten mit try/catch und vorzeitigem Verlassen auftreten.
Lösung:
defer führt den verschachtelten Code am Ende des Scopes aus, bevor aus dem aktuellen Funktionsblock verlassen wird, auch wenn das Verlassen der Funktion früher (durch return, throw, break usw.) erfolgt. Mehrere defer werden in umgekehrter Reihenfolge ihres Auftretens ausgeführt.
Beispielcode:
func readFile() { print("Datei öffnen") defer { print("Datei schließen") } print("Zeile 1 lesen") if Bool.random() { print("Frühzeitige Rückkehr!") return } print("Zeile 2 lesen") } readFile() // In der Konsole wird immer angezeigt: Datei öffnen, ..., Datei schließen (am Ende)
Wichtige Merkmale:
Kann man defer außerhalb von Funktionen verwenden?
Nein, defer ist nur innerhalb von Funktionen, Methoden, Initialisierern und Deinitialisierern erlaubt. Es kann nicht beispielsweise im globalen Dateiraum oder innerhalb eines Codes außerhalb von Funktionen platziert werden.
Was passiert mit defer, wenn im Block eine Ausnahme (throw) auftritt?
defer wird trotzdem ausgeführt. Das ist eines seiner Vorteile — die Ressource wird garantiert freigegeben, auch im Fehlerfall (throw). So wird das Muster der sicheren Ressourcenfreigabe umgesetzt.
In welcher Reihenfolge werden mehrere defer ausgeführt?
Sie werden in umgekehrter Reihenfolge (LIFO) ausgeführt: der defer, der später deklariert wurde, wird zuerst ausgeführt.
Beispielcode:
func test() { defer { print("Erster") } defer { print("Zweiter") } print("Innen") } test() // Gibt aus: "Innen", "Zweiter", "Erster"
Eine Funktion mit mehreren, nicht sequenziell angeordneten defer; das Bereinigen einiger Ressourcen wurde vergessen, Rückkehr aus der Funktion an verschiedenen Stellen. Dies führt zu Ressourcenlecks und einer schwierigen Fehlersuche.
Vorteile: Einheitlichkeit des Codes, „gesammelte“ Bereinigungsschritte an einem Ort.
Nachteile: Es ist schwierig nachzuvollziehen, was ausgeführt wird und in welcher Reihenfolge; Es können Lecks bei Logikfehlern auftreten.
Ein defer wird für jeden wichtigen Schritt gemacht, direkt dort platziert, wo die Ressource initialisiert wird, mit Kommentaren. Der Code wird überprüft.
Vorteile: Garantierte Bereinigung von Ressourcen, klares Verständnis der Reihenfolge der Aktionen.
Nachteile: Erfordert Disziplin und Aufmerksamkeit bei der Hinzufügung neuer Ressourcen und Bereinigungsblöcke.