When to use Defer in Swift

The Swift defer statement allows you to execute code right before a function returns. This post presents a number of example use cases where defer can be used in Swift:

  1. Close Files with defer
  2. Release Locks with defer
  3. Call Completion Blocks Using defer

Close Files with Defer

The defer statement allows you to ensure a block of code is executed at the end of a function, for example, closing a file:

func write(data: Data, offset: Int) throws {
    // Compute the filepath to write to
    let documentsPath = NSSearchPathForDirectoriesInDomains(
        .documentDirectory,
        .userDomainMask,
        true
    )[0]
    
    let filename = "data.txt"
    let filepath = "\(documentsPath)/\(filename)"
    
    // Open a file handle
    guard let file = FileHandle(forUpdatingAtPath: filepath) else {
        throw WriteError.notFound
    }
    
    // Ensure the file is closed at the end of this function
    defer {
        try? file.close()
    }
    
    // Seek to specified offset
    try file.seek(toOffset: UInt64(offset))
    
    // Write data at specified offset
    file.write(data)
}

Release Locks with Defer

A defer statement can ensure critical state is updated before a function returns even if your code has multiple branches and paths, like in the case of a lock:

func criticalSectionWork(task: Task, lock: NSLock) -> Int {
    // lock.unlock() will always be called before
    // the return statement
    defer {
        lock.unlock()
    }
    
    // Handle results
    let result = task.perform()
    
    if result == .success {
        modifyDatabase(task: task) 
        return 0
    }
    else if result == .error { 
        return -1 
    }
    else { 
        return -2 
    }
}

Call Completion Blocks Using Defer

Managing state with multiple branches and cases can lead to error prone code. A defer statement can be used to ensure a completion block is called with the correct variables regardless of the branches taken:

func process(tasks: [Tasks], completion: ([Int]?, Error?) -> Void) {
    var results = [Int]()
    var error: Error?
    
    // completion will be always executed when this function ends,
    // even if there are multiple branches of logic later in the
    // function implementation
    defer {
        completion(results, error)
    }
    
    for task in tasks {
        do {
            results.append(task.perform())
        }
        catch let taskError {
            error = taskError
            break
        }
    }
    
    if let error = error {
        results = nil
    }
}

Defer in Swift

That’s it! By using defer in Swift you can close files, release locks, and call completion blocks.