Swift 中 闭包 与 逃逸闭包 的区别

在 Swift 中,闭包 是一种自包含的功能块,可以在代码中被传递和使用。而 逃逸闭包(Escaping Closure) 是一种特殊类型的闭包,它可以在定义它的函数返回之后仍然被调用。

这两者的区别主要体现在闭包的生命周期和使用场景上。


闭包与逃逸闭包的主要区别

特性 普通闭包(非逃逸闭包) 逃逸闭包
生命周期 必须在函数作用域内完成执行 可以在函数返回后被调用
定义方式 默认行为 需显式声明为 @escaping
使用场景 函数内部的短期任务 异步任务或需要延迟调用的任务
闭包捕获规则 不需要持久化捕获上下文中的变量 闭包可能需要持久化上下文变量的捕获
内存管理 不会引起循环引用 容易引起循环引用(需要使用 [weak self]

普通闭包

普通闭包在函数调用过程中完成执行,其生命周期受限于函数本身,函数结束后闭包就被销毁。

示例:普通闭包

func performAction(action: () -> Void) {
    print("Before action")
    action()
    print("After action")
}

performAction {
    print("Executing action")
}

输出:

Before action
Executing action
After action
  • 闭包 actionperformAction 函数体内执行,函数返回后不再使用。
  • 不需要显式声明 @escaping

逃逸闭包

逃逸闭包在函数返回之后仍然可能被调用,例如异步操作中。因为它超出了函数作用域,因此必须显式声明为 @escaping

示例:逃逸闭包

func performAsyncAction(completion: @escaping () -> Void) {
    print("Starting async operation")
    DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
        completion() // 闭包在函数返回后被调用
    }
    print("Async operation scheduled")
}

performAsyncAction {
    print("Async operation completed")
}

输出:

Starting async operation
Async operation scheduled
Async operation completed
  • 闭包 completion 被存储并在异步操作完成后执行。
  • 必须用 @escaping 标记,否则编译器会报错。

逃逸闭包的应用场景

  1. 异步任务: 网络请求、定时器、线程切换等。
  2. 事件回调: 按钮点击、手势响应等需要延迟执行的操作。

捕获上下文和内存管理

普通闭包的捕获行为

普通闭包通常不会导致循环引用,因为其生命周期与函数作用域一致。

func calculate() {
    let value = 10
    performAction {
        print("Value is \(value)") // 捕获了变量 `value`
    }
}

逃逸闭包的捕获行为

逃逸闭包可能会导致循环引用,特别是在捕获 self 的情况下。为避免此问题,应该使用 [weak self][unowned self]

class MyClass {
    var value = 0
    func performAsyncTask() {
        performAsyncAction { [weak self] in
            guard let self = self else { return }
            print("Value is \(self.value)")
        }
    }
}

总结

  • 普通闭包:仅在函数内执行,不需要 @escaping 标记。
  • 逃逸闭包:函数返回后可能被调用,必须使用 @escaping 标记。
  • 使用逃逸闭包时需特别注意内存管理,防止捕获循环引用的问题。