When and Why to Use @escaping in Swift - A Practical Guide
Ⅰ.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
- The closure is passed as a parameter to a function
- The closure is executed within the function body
- The function exits and the closure is released
Characteristics
- No need for @escaping
- You can omit self inside the closure (no retain cycle risk)
- Closure cannot outlive the function
Example
1 | class HttpTool { |
1 | class ViewController: UIViewController { |
Execution Order:
1 → 2 → 3
Closure runs and completes within the function body.
Ⅲ.Escaping Closures
Lifecycle
- The closure is passed into a function
- The function returns
- 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 | class HttpTool { |
1 | class ViewController: UIViewController { |
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 | func loadData(completion: @escaping (Data?) -> Void) { |
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 | func sortedEvenNumbers(_ numbers: [Int], completion: (Result<[Int], Error>) -> Void) { |
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 | func animateView(_ view: UIView, duration: TimeInterval, completion: @escaping () -> Void) { |
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
- Closures – The Swift Programming Language (Swift 5.9) – Apple Developer Documentation
- Swift Evolution Proposal SE-0279: Multiple Trailing Closures – Swift.org
- Swift中的逃逸闭包(@escaping )与非逃逸闭包(@noescaping) – 掘金社区 (in Chinese)
- Swift闭包:逃逸与非逃逸的区别详解 – CSDN Blog (in Chinese)
- Swift闭包的定义与使用详解 – CSDN Blog (in Chinese)
- Swift闭包语法、使用、逃逸与非逃逸 – CSDN Blog (in Chinese)
- Swift闭包(Closures)学习记录 – CSDN Blog (in Chinese)