基于本文回答

播面 播面

刷题像听歌,多听自然懂
0
评论

@escaping (逃逸闭包) 和 @nonescaping (非逃逸闭包) 的区别?

知识点图片

在 Swift 中,@escaping(逃逸)和 @nonescaping(非逃逸)的核心区别在于闭包(Closure)的生命周期,即闭包是在函数返回之前执行,还是在函数返回之后才执行。

自 Swift 3 开始,默认情况下,所有的闭包参数都是非逃逸的(@nonescaping)

以下是详细的对比和解释:


1. 非逃逸闭包 (@nonescaping) —— 默认

定义:当闭包作为参数传递给函数,该闭包在函数结束(return)之前就被调用并执行完毕。

  • 生命周期

    1. 传递闭包给函数。
    2. 函数内部运行闭包。
    3. 函数返回。
    4. 闭包销毁。
  • 特点

    • 性能优化:编译器知道闭包不会在函数外部存活,因此不需要在堆(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)。
    • 存储:闭包被赋值给函数外部的一个变量或属性,稍后调用。
  • 生命周期

    1. 传递闭包给函数。
    2. 函数执行其他操作(可能将闭包保存在某处)。
    3. 函数返回。
    4. 在未来的某个时间点,闭包被执行。
  • 特点

    • 内存管理:闭包必须被存储在堆(Heap)上,因为它需要比函数活得更久。
    • 强制 self:如果闭包里用到了当前对象的方法或属性,必须显式使用 self.,并且通常需要考虑使用 [weak self] 来避免循环引用(Retain Cycle),因为闭包持有了 self,而 self 可能也持有了闭包。
  • 代码示例

    plaintext
    var 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 将非逃逸作为默认选项是一个安全性能的选择:

  1. 性能:大多数闭包(如数组的 mapsort)都是立即执行的。默认非逃逸可以让编译器进行大量优化,避免不必要的内存分配。
  2. 安全:非逃逸闭包不会导致循环引用。强制开发者在逃逸闭包中显式写 self,是为了提醒开发者:“注意!这个闭包可能会捕获 self 并导致内存泄漏,请检查是否需要弱引用。”
00:00
00:00