The Swift defer statement allows you to execute code right before scope is exited (for example, before a function returns). This post presents an overview of Swift defer and the most common examples for the defer keyword:

  1. Swift Defer Keyword
  2. Defer After Return
  3. Defer Completion
  4. Defer Logic
  5. Defer Return Value

Swift Defer Keyword

The multiple use cases for the Swift defer keyword can be grouped into two main categories:

  1. Defer Completion. Like the Defer Completion example, a defer statement can be used ensure a completion block is called with appropriate values before a function returns.
  2. Defer Logic. Like the Defer Logic example, a defer statement can be used to ensure key logic like closing a file executes before a function returns.

Check out the Swift defer examples in this post to see how the defer keyword can be used defer completion and defer logic.

Defer After Return

In Swift defer will execute after return. Thus, a defer statement can ensure critical state is updated before a function returns even if your code has multiple branches and paths. For example, a lock can be unlocked using a defer statement to ensure the lock is always unlocked before the function return statements:

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

Defer Completion

Managing state with multiple branches and cases can lead to error prone code. A Swift defer statement can be used to ensure a completion block is always called on completion 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 Logic

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)
}

Defer Return Value

An incorrect use of a defer statement in Swift is to attempt to modify the return value. The following example demonstrates why using the defer keyword to modify the return value is not a good solution:

func status() -> String {
    var status = "initial"

    // Modifying a value type in the defer statement
    // will not modify the return value
    defer {
        // This assignment does not work
        status = "active"
    }

    return status
}

// Calling the status function will 
// return "initial"
status()

In some cases, a defer statement is able to modify the return value. For example, logic executed in a defer statement would modify a return value if the return value is a persistent object or reference.

In these cases it is still improper to use defer to modify the return value. Modifying the return value in a defer statement may make debugging difficult, particularly for complex functions. Additionally, if a return value is modified in a defer statement there is often a more logical place for the modification to occur.

Swift Defer Statement

That’s it! By using defer in Swift you can execute important logic after the return, defer logic, and defer completion callbacks to ensure completion blocks are called.