Lesson 85Error Handling
Defer Statement
Guaranteed Cleanup
The defer statement executes code just before leaving the current scope, no matter how you leave it - normal return, early return, or thrown error.
Key Properties
Always Executes
Runs on return, throw, or break
LIFO Order
Multiple defers run in reverse order
Scope-Based
Tied to current scope (function, loop, if)
No Control Transfer
Can't return, break, or throw from defer
Common Use Cases
- File handles - Close files after reading/writing
- Locks - Release locks after critical sections
- Database - Close connections after queries
- Network - Clean up sessions
- Timing - Measure elapsed time
- Logging - Log function exit
Multiple Defers
When you have multiple defer statements, they execute in reverse order (Last In, First Out).
Think of it like a stack - last defer declared is first to execute.
Important Note
defer only runs if its line is reached! If you return before the defer statement, it won't execute.
Best practice: Put defer right after resource acquisition.
main.swift
// DEFER STATEMENT
// Execute code when leaving the current scope
// BASIC DEFER
func processFile() {
print("1. Starting")
defer {
print("4. Cleanup (defer)")
}
print("2. Processing")
print("3. Finishing")
}
processFile()
// Output:
// 1. Starting
// 2. Processing
// 3. Finishing
// 4. Cleanup (defer)
// DEFER WITH ERROR HANDLING
enum FileError: Error { case notFound }
func readAndProcess() throws {
let file = openFile()
defer {
closeFile(file) // Always runs, even if error thrown!
print("File closed")
}
// Process file... might throw
try processContents(file)
}
func openFile() -> Int { return 1 }
func closeFile(_ f: Int) { }
func processContents(_ f: Int) throws { }
// MULTIPLE DEFERS - Execute in REVERSE order (LIFO)
func multipleDefers() {
defer { print("First defer") }
defer { print("Second defer") }
defer { print("Third defer") }
print("Function body")
}
multipleDefers()
// Output:
// Function body
// Third defer
// Second defer
// First defer
// DEFER FOR RESOURCE CLEANUP
class DatabaseConnection {
func open() { print("DB opened") }
func close() { print("DB closed") }
func query(_ sql: String) throws -> [String] { return [] }
}
func fetchUsers() throws -> [String] {
let db = DatabaseConnection()
db.open()
defer {
db.close() // Guaranteed to close!
}
// Multiple operations that might throw
let users = try db.query("SELECT * FROM users")
let admins = try db.query("SELECT * FROM admins")
return users + admins
}
// DEFER WITH LOCKS
import Foundation
class ThreadSafeCounter {
private var value = 0
private let lock = NSLock()
func increment() {
lock.lock()
defer {
lock.unlock() // Always unlock!
}
value += 1
// Even if something goes wrong, lock is released
}
}
// DEFER IN LOOPS
func processItems(_ items: [String]) {
for item in items {
defer {
print("Done with \(item)")
}
print("Processing \(item)")
}
}
processItems(["A", "B", "C"])
// Processing A
// Done with A
// Processing B
// Done with B
// Processing C
// Done with C
// DEFER WITH EARLY RETURNS
func validate(input: String?) -> Bool {
defer {
print("Validation complete")
}
guard let input = input else {
print("Input is nil")
return false // defer still runs!
}
guard !input.isEmpty else {
print("Input is empty")
return false // defer still runs!
}
return true
}
// DEFER FOR TIMING
func measureTime() {
let start = Date()
defer {
let elapsed = Date().timeIntervalSince(start)
print("Elapsed: \(elapsed) seconds")
}
// Do some work...
for _ in 0..<1000000 { }
}
// DEFER WITH CONDITIONALS
func conditionalDefer(shouldCleanup: Bool) {
if shouldCleanup {
defer {
print("Cleanup performed")
}
}
print("Main work")
}
// Note: defer only runs if its scope is entered!
conditionalDefer(shouldCleanup: false) // No cleanup
conditionalDefer(shouldCleanup: true) // Cleanup performed
// PRACTICAL EXAMPLE: File Handling
func writeToFile(data: String) throws {
let file = fopen("/tmp/test.txt", "w")
guard file != nil else {
throw FileError.notFound
}
defer {
fclose(file)
print("File handle closed")
}
// Write data...
fputs(data, file)
}Try It Yourself!
Create a Transaction class that uses defer to automatically rollback if commit isn't called before the scope ends!