前言
闭包是 Swift 的"灵魂语法"之一。它同时承担了
- 函数式编程的高阶函数;
- 面向对象中的委托回调;
- 异步并发中的逃逸闭包;
- 甚至属性包装器与 DSL 的构建基础。
闭包到底是什么?------ 一句话定义
闭包是自包含的代码块,可以在代码里被传递、被调用,同时自动捕获其定义时所在上下文中的常量和变量。
Swift 的闭包有三种形态:
- 全局函数:有名字,不捕获任何值。
- 嵌套函数:有名字,可捕获外层函数局部量。
- 闭包表达式:无名字,轻量级语法,可捕获上下文。
闭包表达式语法拆解
最简形式:
            
            
              swift
              
              
            
          
          { (parameters) -> returnType in
    statements
}下面用"反向排序"一例,把 5 次迭代全部还原
            
            
              swift
              
              
            
          
          // 0. 原始数组
let names = ["Chris", "Alex", "Ewa", "Barry", "Dani"]
// 1. 最原始:写一个普通函数
func backward(_ s1: String, _ s2: String) -> Bool {
    return s1 > s2   // 按字母降序
}
let reversed1 = names.sorted(by: backward)
print(reversed1)   // ["Ewa", "Dani", "Chris", "Barry", "Alex"]
// 2. 第一次简化:写成完整闭包表达式
let reversed2 = names.sorted(by: { (s1: String, s2: String) -> Bool in
    return s1 > s2
})
print(reversed2)
// 3. 第二次简化:类型推断,省略参数/返回类型
let reversed3 = names.sorted(by: { s1, s2 in return s1 > s2 })
print(reversed3)
// 4. 第三次简化:单表达式可省 return
let reversed4 = names.sorted(by: { s1, s2 in s1 > s2 })
print(reversed4)
// 5. 第四次简化:使用 $0、$1 占位符
let reversed5 = names.sorted(by: { $0 > $1 })
print(reversed5)
// 6. 第五次简化:直接传运算符
let reversed6 = names.sorted(by: >)
print(reversed6)Trailing Closure ------ 尾随闭包
当闭包是最后一个参数且较长时,可写在调用括号外,增强可读性。
单参数可省括号;多参数时,第一个尾随闭包可省标签,其余必须带标签。
            
            
              swift
              
              
            
          
          // 0. 原始数组
let names = ["Chris", "Alex", "Ewa", "Barry", "Dani"]
// 单参数,括号直接省
let reversed7 = names.sorted { $0 > $1 }
class Server {}
class Picture {}
let server = Server()
func show(_ picture: Picture) {
    print("一张图片")
}
func show(_ e: Error) {
    print("一个错误")
}
// 多参数:loadPicture
func loadPicture(
    from server: Server,
    completion: @escaping (Picture) -> Void,
    onFailure: @escaping (Error) -> Void
) { /* 网络代码 */ }
loadPicture(from: server) { picture in
    show(picture)
} onFailure: { error in
    show(error)
}捕获值(Capturing Values)------ 闭包的"黑魔法"
嵌套函数/闭包表达式可以延长局部变量的生命周期。
            
            
              swift
              
              
            
          
          func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0          // 外层局部变量
    func incrementer() -> Int {   // 嵌套函数
        runningTotal += amount    // 捕获两个量
        return runningTotal
    }
    return incrementer
}
let inc10 = makeIncrementer(forIncrement: 10)
let inc7  = makeIncrementer(forIncrement: 7)
print(inc10()) // 10
print(inc10()) // 20
print(inc7())  // 7
print(inc10()) // 30 (与 inc7 互不干扰)闭包是引用类型------ 循环引用根源
把闭包赋值给 let 常量时,常量里存的是引用。因此两个变量指向同一份闭包代码+捕获的存储。
            
            
              swift
              
              
            
          
          let alsoInc10 = inc10
alsoInc10() // 40,与 inc10 共享同一 runningTotal逃逸闭包 @escaping
当闭包在函数返回后才被执行,必须显式标记 @escaping。
常见场景:
- 异步回调(网络、动画);
- 存储到外部变量(数组、字典)。
            
            
              swift
              
              
            
          
          nonisolated(unsafe) var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(_ completion: @escaping () -> Void) {
    completionHandlers.append(completion) // 逃出函数作用域
}自动闭包 @autoclosure
自动把表达式包成无参闭包,实现延迟求值。断言、日志、调试框架大量使用。
            
            
              swift
              
              
            
          
          // 自定义 assert 风格函数
func myAssert(_ condition: @autoclosure () -> Bool,
              _ message: @autoclosure () -> String = "") {
    if !condition() {
        print("断言失败: \(message())")
    }
}
myAssert(2 > 3, "2 不大于 3")   // 表达式被包成闭包,只有失败才求值捕获列表 ------ 破解循环引用
当逃逸闭包会捕获 self,而 self 又持有该闭包时,形成强引用环。
用捕获列表 [weak self] 或 [unowned self] 解决。
            
            
              swift
              
              
            
          
          class MyViewController {
    var count = 0
    
    @MainActor
    func delayedPrint() {
        // 逃逸闭包,10 秒后执行
        DispatchQueue.main.asyncAfter(deadline: .now() + 10) { [weak self] in
            guard let self = self else { return }
            print(self.count)
        }
    }
}结构体/枚举的逃逸限制
值类型不允许共享可变状态。
在 mutating 方法里,不能把捕获了 self 的逃逸闭包存起来,否则编译器直接报错。
解决思路:
- 把需要的数据拷贝一份再捕获;
- 改写成类(引用类型)。
实战:用闭包搭一个"迷你 DSL"
利用尾随闭包 + 自动闭包,30 行代码实现一个链式动画框架:
            
            
              swift
              
              
            
          
          // 定义
func animate(
    duration: TimeInterval,
    _ animations: @escaping () -> Void,
    completion: @escaping () -> Void = {}
) {
    UIView.animate(withDuration: duration,
                   animations: animations,
                   completion: { _ in completion() })
}
// 使用
animate(duration: 0.3) {
    view.alpha = 0
} completion: {
    print("消失完成")
}总结与扩展
- 
语法简写顺序 完整 → 省类型 → 省 return → 占位符 → 运算符。 
- 
捕获是"隐形延长生命周期",但逃逸必须显式声明。 
- 
值类型想逃逸,先复制;类类型想逃逸,先考虑 [weak/unowned]。
- 
自动闭包是"懒人包",断言、日志、路由延迟求值神器。 
- 
真实项目常见坑 - 网络层把 @escaping漏写,升级 Swift 直接编译失败;
- flatMap/- compactMap里写- $0导致可读性变差,团队规范要求>2 个参数必须显式命名;
 
- 网络层把