问题的历史:
逃逸分析是编译器优化和内存管理中的一个术语。在Swift中,由于闭包和ARC的广泛使用,它变得非常重要。它与逃逸(escaping)和非逃逸(non-escaping)闭包的概念有关,定义了闭包是否可以超出函数的作用域。
问题:
错误地确定闭包类型会导致内存拥有权错误、内存泄漏、意外捕获变量和性能下降。需要明确了解何时闭包“逃逸”(escapes),何时不逃逸,并正确标记它们。
解决方案:
Swift要求使用@escaping属性显式标记逃逸闭包。非逃逸闭包只能在函数内部被捕获,它们自动具有更高效的内存管理,并且可以更安全地使用捕获的变量。
示例差异:
// 非逃逸闭包(更快,调用后不在内存中存储) func performSync(block: () -> Void) { block() } // 逃逸闭包(可以被存储并在之后执行) var storedCompletion: (() -> Void)? func performAsync(block: @escaping () -> Void) { storedCompletion = block }
关键特性:
@escaping是否可以在不更改函数源代码的情况下将定义为非逃逸的闭包改为逃逸?
不可以。@escaping属性必须在函数签名中显式指定。如果函数内部的闭包传递到函数外部——仅需要逃逸闭包,否则编译器会报错。
在没有weak/unowned捕获的情况下将self传递到逃逸闭包中是安全的吗?
不安全。如果闭包使用强引用捕获self,逃逸闭包可能会创建retain cycle。需要明确控制捕获:
someAsync { [weak self] in self?.doSomething() }
逃逸闭包是否总是全局的(在程序结束前保持在内存中)?
不是。它的生命周期取决于存储范围。如果闭包仅被暂时保留或属性被设置为nil,它将在属性失去所有者时被释放。只有那些持有对全局对象的引用或在全局变量中存储的闭包才会变为全局闭包。
@escaping属性在类的方法内部保存闭包而没有@escaping(编译器错误),后来更改,忘记了防止retain cycle——应用程序的内存泄漏。
优点:
缺点:
开发人员始终检查何时需要逃逸闭包,以weak/unowned的方式捕获self,避免内存泄漏,安全地管理内存。
优点:
缺点: