defer — μια δομή ελέγχου στη Swift που προγραμματίζει την εκτέλεση ενός μπλοκ κώδικα τη στιγμή εξόδου από το τρέχον πεδίο ορατότητας (scope). Το μπλοκ defer εκτελείται ανεξάρτητα από τον τρόπο τερματισμού — return, break, throw, fatalError ή κανονικός τερματισμός. Σύμφωνα με τον Swift Language Guide (2025), όταν υπάρχουν πολλαπλά defer στο ίδιο scope, εκτελούνται με αντίστροφη σειρά δήλωσης — το τελευταίο που δηλώθηκε εκτελείται πρώτο (LIFO). Αυτό καθιστά το defer απαραίτητο για τον εγγυημένο καθαρισμό πόρων: κλείσιμο περιγραφέων αρχείων, απελευθέρωση κλειδωμάτων, αποδέσμευση προσωρινών δεικτών χωρίς τον κίνδυνο παράλειψης cleanup πρόωρης εξόδου.
Κύρια
defer — είναι μια δομή ελέγχου της γλώσσας Swift, που εισήχθη στη Swift 2.0 (2015), η οποία καθυστερεί την εκτέλεση του μπλοκ της μέχρι τη στιγμή ολοκλήρωσης του τρέχοντος scope. Βασικό χαρακτηριστικό: το defer εγγυάται την εκτέλεση του σώματός του ανεξάρτητα από το πώς ακριβώς ολοκληρώνεται το scope — επιτυχώς (return), με σφάλμα (throw), πρόωρα (break, continue) ή μοιραία (fatalError, precondition).
Συντακτικά το defer μοιάζει με defer { /* κώδικας */ } και μπορεί να τοποθετηθεί οπουδήποτε μέσα στο scope. Ο μεταγλωττιστής Swift εγγυάται ότι ο κώδικας μέσα στο defer θα εκτελεστεί, ακόμα κι αν μεταξύ της δήλωσης του defer και του τέλους του scope συμβεί μια εξαίρεση ή return. Αυτό διαφοροποιεί θεμελιωδώς το defer από τον συνηθισμένο κώδικα που τοποθετείται στο τέλος μιας συνάρτησης, ο οποίος μπορεί να παραλειφθεί σε πρόωρη έξοδο.
Σύμφωνα με το άρθρο του Chris Lattner (Δημιουργός Swift, 2015), το defer εμπνεύστηκε από ανάλογες δομές σε άλλες γλώσσες — defer στη Go, finally στη Java/Python, scope guard στη C++ — αλλά με μια σημαντική διαφορά: στη Swift το defer εκτελείται στο τέλος του scope, όχι αμέσως μετά το μπλοκ try-catch. Αυτό δίνει πιο προβλέψιμη συμπεριφορά για το cleanup σε συναρτήσεις με πολλαπλά σημεία εξόδου.
Χρησιμοποιήστε το defer για συμμετρική διαχείριση πόρων: άνοιγμα αρχείου → defer { close }, ρύθμιση κλειδώματος → defer { unlock }. Ένα τέτοιο μοτίβο εγγυάται ότι η αποδέσμευση του πόρου δεν θα παραλειφθεί σε καμία περίπτωση.
Όταν δηλώνονται πολλαπλά defer στο ίδιο scope, εκτελούνται με αντίστροφη σειρά δήλωσης (LIFO — Last In, First Out). Αυτό σημαίνει ότι το τελευταίο δηλωμένο defer θα εκτελεστεί πρώτο, και το πρώτο — τελευταίο:
func exampleDeferOrder() {
defer { print("Πρώτο defer") }
defer { print("Δεύτερο defer") }
defer { print("Τρίτο defer") }
print("Σώμα συνάρτησης")
}
// Έξοδος:
// Σώμα συνάρτησης
// Τρίτο defer
// Δεύτερο defer
// Πρώτο defer
Η σειρά LIFO είναι σημαντική για τη σωστή διαχείριση ένθετων πόρων. Εάν πρώτα ανοίγει το αρχείο Α, στη συνέχεια το αρχείο Β, πρέπει να αποδεσμευτούν με αντίστροφη σειρά: πρώτα Β, μετά Α. Με το defer αυτό συμβαίνει αυτόματα — δηλώστε το defer αμέσως μετά το άνοιγμα κάθε πόρου, και η σειρά καθαρισμού θα είναι σωστή ανεξάρτητα από τον αριθμό των σημείων εξόδου από τη συνάρτηση.
Σύμφωνα με το Swift by Sundell (2024), αυτό το χαρακτηριστικό καθιστά το defer ιδανικό για ένθετα κλειδώματα και συναλλαγές: λήψη κλειδώματος → defer { unlock } → λήψη επόμενου → defer { unlock }. Το LIFO εγγυάται ότι τα κλειδώματα απελευθερώνονται με αντίστροφη σειρά λήψης, αποτρέποντας αδιέξοδα.
Η κύρια εφαρμογή του defer — εγγυημένος καθαρισμός πόρων Ας εξετάσουμε την εργασία με το σύστημα αρχείων. Το άνοιγμα ενός αρχείου μέσω FileHandle απαιτεί ρητό κλείσιμο — το defer εγγυάται ότι το close θα κληθεί σε οποιοδήποτε σενάριο:
func readFile(path: String) throws -> String {
let handle = try FileHandle(forReadingFrom: URL(fileURLWithPath: path))
defer { try? handle.close() }
let data = try handle.readToEnd()
guard let data else { throw FileError.empty() }
return String(data: data, encoding: .utf8) ?? ""
// Το handle.close() θα κληθεί ακόμα και σε throw ή return
}
Ένα άλλο τυπικό σενάριο — κινουμένα σχέδια UI με σημαία φόρτωσης. Πριν από την έναρξη φόρτωσης ορίζεται η σημαία isLoading = true, και το defer την αλλάζει σε false κατά την έξοδο από τη συνάρτηση, ανεξάρτητα από την επιτυχία ή το σφάλμα του αιτήματος. Αυτό αποτρέπει την κατάσταση όπου η σημαία παραμένει true λόγω μη επεξεργασμένου σφάλματος και μπλοκάρει τη διεπαφή για πάντα.
Σύμφωνα με το Bitbucket Engineering Blog (2024), το defer χρησιμοποιείται επίσης για προφίλ: στην αρχή της συνάρτησης μπορεί να καταγραφεί ο χρόνος, και στο defer — να υπολογιστεί και να εμφανιστεί η διαφορά. Αυτό δίνει ακριβείς μετρήσεις απόδοσης όλων των διαδρομών εκτέλεσης, συμπεριλαμβανομένων των σφαλμάτων.
defer συνδυάζεται αποτελεσματικά με συναρτήσεις throws. Όταν μια συνάρτηση μπορεί να πετάξει σφάλμα σε οποιοδήποτε στάδιο, το defer εγγυάται το cleanup χωρίς αντιγραφή κώδικα σε κάθε μπλοκ catch ή πρόωρη έξοδο guard:
func processTransaction() throws {
let db = try openDatabase()
defer { closeDatabase(db) }
let user = try fetchUser(from: db)
defer { logAudit(user) }
let result = try performPayment(user)
sendNotification(result)
// Το closeDatabase(db) και logAudit(user) θα κληθούν
// σε κάθε throw ή return
}
Σημαντικό: το defer εκτελείται πριν από τη μεταφορά ελέγχου από το μπλοκ catch, αλλά μετά από την εμφάνιση του σφάλματος. Εάν στο defer πεταχτεί σφάλμα, η Swift δεν επιτρέπει την άμεση χρήση του try μέσα στο defer — απαιτείται try? ή try!. Σύμφωνα με την Apple Documentation, η Swift δεν επιτρέπει στο σφάλμα να «βγει» από το defer, επειδή αυτό θα παραβίαζε την εγγύηση εκτέλεσης του μπλοκ.
Τοποθετήστε το defer αμέσως μετά τη λήψη του πόρου. Αυτό ακολουθεί την αρχή της εγγύτητας: ο αναγνώστης βλέπει τη λήψη και την αποδέσμευση δίπλα-δίπλα, αυξάνοντας την αξιοπιστία του κώδικα και απλοποιώντας την αναθεώρηση κώδικα.
defer εκτελείται κατά την έξοδο από το scope στο οποίο δηλώθηκε. Εάν το defer δηλωθεί μέσα σε ένα μπλοκ do, εκτελείται κατά την έξοδο από αυτό το μπλοκ, όχι από την εξωτερική συνάρτηση. Εάν μέσα σε έναν βρόχο for — σε κάθε επανάληψη:
func scopeExample() {
print("start")
do {
defer { print("defer σε μπλοκ do") }
print("inside do")
}
// "defer σε μπλοκ do" εκτυπώνει εδώ
print("after do")
}
// Έξοδος: start, inside do, defer σε μπλοκ do, after do
for i in 1...3 {
defer { print("end iteration \(i)") }
print("iteration \(i)")
}
// Έξοδος: επανάληψη 1, τέλος επανάληψης 1, επανάληψη 2, τέλος επανάληψης 2, ...
Οι μεταβλητές που συλλαμβάνονται από το defer διαβάζονται τη στιγμή εξόδου από το scope, όχι τη στιγμή δήλωσης του defer. Εάν μια μεταβλητή αλλάξει μεταξύ της δήλωσης του defer και του τέλους του scope, το defer θα δει την τελευταία τιμή. Αυτή είναι μια σημαντική διαφορά από τα closures, όπου η σύλληψη συμβαίνει τη στιγμή δημιουργίας. Να είστε προσεκτικοί: οι αλλαγές της μεταβλητής μετά τη δήλωση του defer θα επηρεάσουν την εκτέλεσή του.
Πρώτο λάθος — υπόθεση για διαφορετική σειρά εκτέλεσης από LIFO. Εάν η σειρά cleanup είναι σημαντική και τα defer έχουν δηλωθεί σε λάθος σειρά, οι πόροι μπορεί να αποδεσμευτούν με παραβίαση εξαρτήσεων. Λύση: δηλώστε το defer αμέσως μετά τη λήψη κάθε πόρου. Δεύτερος πόρος άνοιξε → defer { close second } πριν κλείσει ο πρώτος.
Δεύτερο λάθος — χρήση του defer για λογική που δεν σχετίζεται με τον καθαρισμό. Το defer προορίζεται για εγγυημένο cleanup, όχι για την κύρια ροή ελέγχου. Εάν ο κώδικας στο defer επηρεάζει την επιστρεφόμενη τιμή, αυτό είναι σχεδόν πάντα λάθος. Το defer δεν μπορεί να αλλάξει την return τιμή της συνάρτησης (σε αντίθεση με το Java finally, όπου το return στο finally αντικαθιστά το αρχικό return).
Τρίτο λάθος — ρίψη σφάλματος από το defer. Η Swift απαγορεύει το try μέσα στο defer εάν το σφάλμα μπορεί να διαδοθεί προς τα έξω. Χρησιμοποιήστε try? ή try! για λειτουργίες που μπορεί να πετάξουν σφάλμα, ή τυλίξτε τις σε ξεχωριστή συνάρτηση χωρίς throws. Σύμφωνα με το O'Reilly „Swift in Depth" (2025), καλή πρακτική είναι να κάνετε τις συναρτήσεις cleanup non-throwing ή να χειρίζεστε τα σφάλματα μέσα στο defer.
Συχνές Ερωτήσεις
defer — μια δομή ελέγχου Swift που καθυστερεί την εκτέλεση του μπλοκ μέχρι την έξοδο από το τρέχον πεδίο ορατότητας. Το μπλοκ εκτελείται πάντα — σε return, throw, break ή κανονικό τερματισμό. Χρησιμοποιείται για εγγυημένο καθαρισμό πόρων: κλείσιμο αρχείων, απελευθέρωση κλειδωμάτων.
Με αντίστροφη σειρά δήλωσης (LIFO) — το τελευταίο δηλωμένο defer εκτελείται πρώτο. Αυτό εγγυάται τον σωστό καθαρισμό ένθετων πόρων: εάν ο πόρος Β άνοιξε μετά τον Α, θα κλείσει πριν από τον Α, αποτρέποντας εξαρτήσεις από ήδη αποδεσμευμένους πόρους.
Όχι άμεσα — η Swift απαγορεύει τη διάδοση σφάλματος από το defer. Χρησιμοποιήστε try? ή try! για λειτουργίες που μπορεί να πετάξουν σφάλμα. Η καλύτερη πρακτική είναι να κάνετε τις συναρτήσεις cleanup non-throwing ή να χειρίζεστε τα σφάλματα μέσα στο defer χωρίς διάδοση προς τα έξω.
defer είναι δεμένο με το scope και εκτελείται σε κάθε έξοδο, συμπεριλαμβανομένων των return, throw και break. Το finally (σε άλλες γλώσσες) είναι δεμένο με το try-catch και εκτελείται μόνο με την παρουσία try. Στη Swift δεν υπάρχει finally — το defer καλύπτει πλήρως αυτό το σενάριο και λειτουργεί για οποιοδήποτε scope, όχι μόνο για χειρισμό σφαλμάτων.
Ναι, το defer διαβάζει μεταβλητές τη στιγμή εξόδου από το scope, όχι τη στιγμή δήλωσης. Εάν μια μεταβλητή αλλάξει μετά τη δήλωση του defer, το μπλοκ defer θα δει την τελευταία τιμή. Αυτό διαφέρει από τα συνηθισμένα closures, όπου η σύλληψη καθορίζεται τη στιγμή δημιουργίας.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης