Ⅰ.Introduction

In my last article, Escaping vs Non-Escaping Closures in Swift, we discussed the difference between escaping and non-escaping closures in Swift. Today, we’ll take a deeper look at escaping closures through real-world examples.

Core Concept Sentence:

A non-escaping closure executes during the function execution, while an escaping closure executes after the function has returned.

Ⅱ.Non-Escaping Closures

Lifecycle

  1. The closure is passed as a parameter to a function
  2. The closure is executed within the function body
  3. The function exits and the closure is released

Characteristics

  1. No need for @escaping
  2. You can omit self inside the closure (no retain cycle risk)
  3. Closure cannot outlive the function

Example

1
2
3
4
5
class HttpTool {
func loadData(callBack: (String) -> Void) {
callBack("Non-escaping closure")
}
}
1
2
3
4
5
6
7
8
9
10
class ViewController: UIViewController {
let tools = HttpTool()

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
tools.loadData { jsonData in
print(jsonData)
// Safe to access properties without `self`
}
}
}
Execution Order:

1 → 2 → 3
Closure runs and completes within the function body.

Ⅲ.Escaping Closures

Lifecycle

  1. The closure is passed into a function
  2. The function returns
  3. The closure is executed later, outside the function’s scope

Its lifetime exceeds the function’s scope

Must Be Marked with @escaping

Without this annotation, the compiler will throw an error:

“Closure is escaping but is not marked with ‘@escaping’”

Use of self

Since the closure can outlive the current scope, you must use self explicitly inside the closure, and it’s recommended to use [weak self] to avoid retain cycles.

Example

1
2
3
4
5
6
7
8
9
class HttpTool {
func loadData(callBack: @escaping (String) -> Void) {
DispatchQueue.global().async {
DispatchQueue.main.async {
callBack("This is an escaping closure")
}
}
}
}
1
2
3
4
5
6
7
8
9
10
class ViewController: UIViewController {
let tools = HttpTool()

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
tools.loadData { [weak self] jsonData in
print(jsonData)
self?.title = jsonData
}
}
}

Execution Order:

1 → 3 → 2
The closure is executed after the function returns, as part of an asynchronous task.

Ⅳ.Real-World iOS Development Examples:

1. Network Request Completion Handlers

1
2
3
4
5
6
7
8
func loadData(completion: @escaping (Data?) -> Void) {
DispatchQueue.global().async {
let data = try? Data(contentsOf: url)
DispatchQueue.main.async {
completion(data)
}
}
}

The closure is executed after the loadData function returns, so it must be marked as @escaping. This is typical in asynchronous operations like network requests or API callbacks.

2. Synchronous Computation Callbacks

In contrast to network requests, synchronous computations or quick logic processing execute the closure directly within the function body — no need for @escaping.

1
2
3
4
func sortedEvenNumbers(_ numbers: [Int], completion: (Result<[Int], Error>) -> Void) {
let evens = numbers.filter { $0 % 2 == 0 }.sorted()
completion(.success(evens))
}

The closure is executed immediately during the function call — it’s non-escaping (default behavior). There’s no asynchronous context.

3. Async Event Wrappers (Animations, Delays, etc.)

1
2
3
4
5
6
7
func animateView(_ view: UIView, duration: TimeInterval, completion: @escaping () -> Void) {
UIView.animate(withDuration: duration) {
view.alpha = 0
} completion: { _ in
completion()
}
}

The completion closure is stored by the animation engine and called after the animation ends — this requires @escaping.

Ⅴ.Summary

Core insight:

Non‑escaping closures execute during the function call. Escaping closures execute after the function has returned.

You can think of a closure inside a function as a part of that function’s logic.

If it’s a non-escaping closure, it gets executed during the function call itself — it’s tightly scoped and finishes within the function.

But if it’s an escaping closure, that means the closure doesn’t execute within the function body. Instead, it “escapes” and runs after the function has returned.

This typically happens in time-consuming tasks like network requests or asynchronous operations, where the closure is called later — after the function has finished “exporting” its main task.

Escaping closures are deferred — they execute after the function ends, while non-escaping closures are immediate and inline.

Ⅵ.References


  1. Closures – The Swift Programming Language (Swift 5.9)Apple Developer Documentation
  2. Swift Evolution Proposal SE-0279: Multiple Trailing ClosuresSwift.org
  3. Swift中的逃逸闭包(@escaping )与非逃逸闭包(@noescaping)掘金社区 (in Chinese)
  4. Swift闭包:逃逸与非逃逸的区别详解CSDN Blog (in Chinese)
  5. Swift闭包的定义与使用详解CSDN Blog (in Chinese)
  6. Swift闭包语法、使用、逃逸与非逃逸CSDN Blog (in Chinese)
  7. Swift闭包(Closures)学习记录CSDN Blog (in Chinese)