闭包
全面解析 Swift 中的闭包特性,涵盖基础语法、内存管理、逃逸闭包以及并发环境下的 @Sendable 和隔离机制。
闭包是 Swift 中功能强大的自包含代码块,可以在代码中被传递和使用。Swift 6.2 进一步增强了闭包在并发环境下的安全性和表达能力。本文将深入探讨闭包的核心概念及其在现代 Swift 开发中的应用。
基础语法与简写
Swift 提供了简洁的闭包语法。系统会自动推断参数和返回值类型,允许省略 return 关键字,并支持尾随闭包语法。
let numbers = [5, 2, 9, 1, 7]
// 完整写法
let sorted1 = numbers.sorted(by: { (s1: Int, s2: Int) -> Bool in
return s1 < s2
})
// 简写语法:类型推断、隐式返回、参数名称缩写
let sorted2 = numbers.sorted(by: { $0 < $1 })
// 尾随闭包:作为最后一个参数时可写在括号外
let sorted3 = numbers.sorted { $0 < $1 }捕获列表与内存管理
闭包默认会强引用其捕获的外部变量。在类实例中使用闭包时,容易造成循环引用。通过捕获列表(Capture List),我们可以定义捕获规则来打破循环引用。
weak 与 unowned
weak:将捕获的引用变为弱引用,类型为可选值(Optional),引用对象释放后变为nil。unowned:将捕获的引用变为无主引用,非可选值,若引用对象已释放,访问会导致崩溃。
class DataManager {
var data: [String] = []
var onUpdate: (() -> Void)?
func setup() {
// 使用 [weak self] 避免循环引用
onUpdate = { [weak self] in
guard let self else { return }
print("Data count: \(self.data.count)")
}
}
deinit {
print("DataManager deinitialized")
}
}逃逸闭包 (@escaping)
当闭包作为参数传递给函数,但在函数返回后才被执行时,该闭包必须标记为 @escaping。这常见于异步操作 completion handler 中。
var completionHandlers: [() -> Void] = []
func performAsyncOperation(completion: @escaping () -> Void) {
// 闭包被存储,稍后执行
completionHandlers.append(completion)
}
func performSyncOperation(closure: () -> Void) {
// 闭包在函数内直接执行,无需 @escaping
closure()
}自动闭包 (@autoclosure)
@autoclosure 允许将一个表达式自动封装为闭包。它不接受参数,被调用时返回表达式的值。这种特性常用于延迟求值,例如 assert 函数。
// condition 参数自动封装为闭包,仅在需要时执行
func logIfTrue(_ condition: @autoclosure () -> Bool, message: String) {
if condition() {
print(message)
}
}
// 调用时直接传入表达式
logIfTrue(2 > 1, message: "Condition is true")Swift 6 并发与闭包
Swift 6 引入了严格的并发检查,闭包在并发环境下的行为受到了更严格的约束。
@Sendable 闭包
@Sendable 标记表示闭包可以在并发域之间安全传递。这要求闭包捕获的所有值也必须是 Sendable 的。
// 定义一个 Sendable 闭包类型
let concurrentOperation: @Sendable (Int) -> Void
concurrentOperation = { id in
print("Processing \(id)")
}
// Task 初始化器接受 @Sendable 闭包
Task {
concurrentOperation(1)
}如果闭包捕获了非 Sendable 的可变状态,编译器会报错,从而防止数据竞争。
闭包的 Actor 隔离
Swift 6 中,闭包会根据上下文继承 Actor 隔离。
- 非隔离闭包:默认情况下,普通闭包不继承 Actor 隔离。
- 隔离继承:使用
@MainActor或其他 Actor 标记的函数中定义的闭包,在某些情况下会继承隔离性。
@MainActor
class ViewModel {
var state = "Idle"
func update() {
// Task 继承 @MainActor 隔离,可以安全访问 state
Task {
state = "Updating"
try? await Task.sleep(nanoseconds: 1_000_000_000)
state = "Done"
}
}
func performDetached() {
// detached 任务不继承隔离,访问 state 需要 await
Task.detached {
await self.updateState("Detached")
}
}
func updateState(_ newState: String) {
state = newState
}
}隔离断言 (#isolation)
Swift 6.2 引入了 #isolation 宏,用于在运行时获取当前闭包的隔离上下文。这对于调试和编写通用的并发代码非常有用。
func checkIsolation() {
print("Current isolation: \(String(describing: #isolation))")
}
Task { @MainActor in
// 输出 MainActor 实例
checkIsolation()
}总结
Swift 的闭包不仅保持了语法的简洁性,更通过 @Sendable 和 Actor 隔离机制,成为了并发编程的安全基石。掌握捕获列表、逃逸闭包以及并发特性,是编写高质量 Swift 代码的关键。
上次更新于