← Back to archive

Development · Swift

Escaping closures in Swift

Understand closure lifetimes, captures, and memory management when work continues after a function returns.

Read the Korean original on Velog ↗English translation

Development archive cover

Whether a closure runs before or after its enclosing function returns affects its lifetime and captures. @escaping makes that longer lifetime explicit.

Nonescaping closures

By default, a closure parameter cannot outlive the function call. The function must use it without storing it for later execution.

class EscapingViewModel: ObservableObject {
    
    @Published var text: String = "Hello"
    
    func getData() {
        downloadData { data in
            text = data
        }
    }
    
    func downloadData(completion: (_ data: String) -> Void) {
    	// Run the closure before returning
        completion("New Data!")
    }
}

Escaping closures

An @escaping closure can be stored or called after the function returns. This is common in asynchronous operations that deliver a result later.

class EscapingViewModel: ObservableObject {
    
    @Published var text: String = "Hello"
    
    func getData() {
    	// Capture weakly for asynchronous work
        downloadData { [weak self] data in
            self?.text = data
        }
    }
    
    func downloadData(completion: @escaping (_ data: String) -> Void) {
	    // Run the closure two seconds after the function returns
        DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
            completion("New Data!")
        }
    }
}

Models and type aliases

A data model makes related values easier to access and pass together.

class EscapingViewModel: ObservableObject {
    
    @Published var text: String = "Hello"
    
    func getData() {
        downloadData { [weak self] DownloadResult in
        	// Access the data
            self?.text = DownloadResult.data
        }
    }
    
    func downloadData(completion: @escaping (DownloadResult) -> Void) {
        DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
        	let result = DownloadResult(data: "New Data")
            completion(result)
        }
    }
}

// Create the data model
struct DownloadResult {
    let data: String
}

A typealias can give a closure type a concise, descriptive name.

class EscapingViewModel: ObservableObject {
    
    @Published var text: String = "Hello"
    
    func getData() {
        downloadData { [weak self] DownloadResult in
            self?.text = DownloadResult.data
        }
    }
    
    // Closure parameter using the type alias
    func downloadData(completion: @escaping DownloadCompletion) {
        DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
        	let result = DownloadResult(data: "New Data")
            completion(result)
        }
    }
}

struct DownloadResult {
    let data: String
}

// Define a type alias
typealias DownloadCompletion = (DownloadResult) -> Void
← Back to archive