@escaping (逃逸闭包) 和 @nonescaping (非逃逸闭包) 的区别?
在 Swift 中,@escaping(逃逸)和 @nonescaping(非逃逸)的核心区别在于闭包(Closure)的生命周期,即闭包是在函数返回之前执行,还是在函数返回之后才执行。
自 Swift 3 开始,默认情况下,所有的闭包参数都是非逃逸的(@nonescaping)。
以下是详细的对比和解释:
1. 非逃逸闭包 (@nonescaping) —— 默认
定义:当闭包作为参数传递给函数,该闭包在函数结束(return)之前就被调用并执行完毕。
生命周期:
- 传递闭包给函数。
- 函数内部运行闭包。
- 函数返回。
- 闭包销毁。
特点:
- 性能优化:编译器知道闭包不会在函数外部存活,因此不需要在堆(Heap)上分配内存来存储闭包的上下文,也不需要进行复杂的引用计数管理(Retain/Release),直接在栈(Stack)上处理,速度更快。
- 无需
self:在闭包内部引用self时,不需要显式写self.,也不用担心循环引用(Retain Cycle),因为闭包会在self销毁前就执行完了。
代码示例:
plaintext// 这里的 completion 默认就是 @nonescaping func doSomething(completion: () -> Void) { print("1. 函数开始") completion() // 必须在这里或者函数结束前执行 print("3. 函数即将返回") } // 调用 doSomething { print("2. 闭包执行") } // 输出顺序: 1 -> 2 -> 3
2. 逃逸闭包 (@escaping)
定义:当闭包作为参数传递给函数,但在函数返回之后才被调用。闭包“逃”出了函数的作用域。
常见场景:
- 异步操作:网络请求成功后的回调(Completion Handler)。
- 存储:闭包被赋值给函数外部的一个变量或属性,稍后调用。
生命周期:
- 传递闭包给函数。
- 函数执行其他操作(可能将闭包保存在某处)。
- 函数返回。
- 在未来的某个时间点,闭包被执行。
特点:
- 内存管理:闭包必须被存储在堆(Heap)上,因为它需要比函数活得更久。
- 强制
self:如果闭包里用到了当前对象的方法或属性,必须显式使用self.,并且通常需要考虑使用[weak self]来避免循环引用(Retain Cycle),因为闭包持有了self,而self可能也持有了闭包。
代码示例:
plaintextvar completionHandlers: [() -> Void] = [] // 必须标记为 @escaping,因为闭包被存到了数组里,函数返回后它还存在 func appendCompletion(completion: @escaping () -> Void) { completionHandlers.append(completion) } // 异步示例 func doAsyncWork(completion: @escaping () -> Void) { print("1. 开启异步任务") DispatchQueue.main.asyncAfter(deadline: .now() + 1) { // 函数早已返回,1秒后才执行这里 print("3. 异步闭包执行") completion() } print("2. 函数返回") } // 调用 doAsyncWork { print("4. 任务完成") } // 输出顺序: 1 -> 2 -> (等待1秒) -> 3 -> 4
3. 总结对比表
| 特性 | 非逃逸 (@nonescaping) | 逃逸 (@escaping) |
|---|---|---|
| 关键字 | 默认(不需要写) | 必须显式标注 @escaping |
| 执行时机 | 函数返回前 | 函数返回后 (通常) |
| 内存分配 | 通常在栈上 (Stack),性能高 | 在堆上 (Heap),需要引用计数 |
| 循环引用风险 | 无 (不需要 [weak self]) |
有 (通常需要 [weak self]) |
| 引用 self | 隐式 (可以直接用属性名) | 显式 (必须写 self.属性名) |
| 典型用途 | map, filter, 立即执行的计算 |
网络请求回调, 延时操作, 委托模式 |
4. 为什么要区分?
Swift 将非逃逸作为默认选项是一个安全和性能的选择:
- 性能:大多数闭包(如数组的
map、sort)都是立即执行的。默认非逃逸可以让编译器进行大量优化,避免不必要的内存分配。 - 安全:非逃逸闭包不会导致循环引用。强制开发者在逃逸闭包中显式写
self,是为了提醒开发者:“注意!这个闭包可能会捕获self并导致内存泄漏,请检查是否需要弱引用。”