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

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