Swift 中的 @escaping 详解 #
@escaping
是 Swift 中用于标记闭包参数的一个重要属性,它表示该闭包可能在函数返回后被调用(即"逃逸"出函数的作用域)。
使用场景 #
1. 异步操作 #
最常见的场景是在异步操作中,闭包被存储起来稍后调用:
func fetchData(completion: @escaping (Result<Data, Error>) -> Void) {
URLSession.shared.dataTask(with: url) { data, _, error in
if let error = error {
completion(.failure(error))
} else if let data = data {
completion(.success(data))
}
}.resume()
}
2. 存储闭包 #
当需要将闭包存储在属性中,以便稍后调用时:
class DataManager {
var completionHandlers: [() -> Void] = []
func store(completion: @escaping () -> Void) {
completionHandlers.append(completion)
}
}
3. 延迟执行 #
使用 DispatchQueue
延迟执行闭包:
func delayExecution(completion: @escaping () -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
completion()
}
}
使用方式 #
在函数参数中的闭包前添加 @escaping
关键字:
func doSomething(completion: @escaping () -> Void) {
// 存储或异步调用 completion
}
好处 #
- 明确性:清楚地告诉其他开发者这个闭包可能会在函数返回后被调用
- 内存管理:强制开发者考虑闭包捕获的值的生命周期
- 编译器检查:编译器会确保你正确处理逃逸闭包的内存管理
- 避免循环引用:提醒开发者需要使用
[weak self]
或[unowned self]
来避免强引用循环
注意事项 #
- 内存管理:逃逸闭包会延长其捕获的任何值的生命周期,可能导致循环引用
self
捕获:在逃逸闭包中使用self
时,通常需要使用弱引用:
class MyClass {
var value = 10
func doWork(completion: @escaping () -> Void) {
DispatchQueue.global().async {
// 这里必须显式引用 self
print(self.value)
completion()
}
}
func safeDoWork(completion: @escaping () -> Void) {
DispatchQueue.global().async { [weak self] in
guard let self = self else { return }
print(self.value)
completion()
}
}
}
- 与非逃逸闭包的区别:默认情况下,闭包参数是非逃逸的(
@noescape
),这意味着它们必须在函数返回前被调用
总结 #
@escaping
是 Swift 中处理异步编程和延迟执行的重要机制,它通过明确的语法标记帮助开发者更好地管理闭包的生命周期和内存使用。理解并正确使用 @escaping
对于编写安全、高效的 Swift 代码至关重要。