@autoclosure:把"表达式"包成"闭包",实现"短路求值"
- 场景回顾
            
            
              swift
              
              
            
          
          /// 自己写的 assert(简化版)
func myAssert(_ condition: Bool, _ message: String) {
    if !condition { print("❌ \(message)") }
}
myAssert(2 > 3, "2 不可能大于 3")   // 无论断言是否成功,message 都会被求值问题:
- 字符串先拼接完成,再传进函数------性能浪费。
- 若拼接代价高("计算成本:\(expensive())"),则每次调用都白算。
- 用 @autoclosure延迟求值
            
            
              swift
              
              
            
          
          func smartAssert(
    _ condition: Bool,
    _ message: @autoclosure () -> String = ""
) {
    if !condition { print("❌ \(message())") }
}
func expensive() -> String {
    "一个非常昂贵的算法"
}
smartAssert(2 > 3, "2 大于 3 的成本:\(expensive())")- 只有当 condition == false时,message()才被真正执行。
- 调用方写法与普通传参一样,无需写大括号------这是 @autoclosure的语法糖核心。
- 官方用法对照
- assert(condition:)
- fatalError(message:)
- os_log的- message参数
rethrows:让"函数参数"的异常传播出去
- 背景
            
            
              swift
              
              
            
          
          func map<T>( _ array: [Int], _ transform: (Int) throws -> T) rethrows -> [T] {
    try array.map(transform)
}- 如果 transform不抛错,map也不会抛;
- 如果 transform抛错,map再把异常原路抛回给调用者。
- 与 throws的区别
| 关键字 | 谁可能抛错 | 调用方必须用 try | 
|---|---|---|
| throws | 函数本身 | ✅ | 
| rethrows | 仅闭包参数 | ✅(但闭包不抛就隐式免 try) | 
- 实现 tryMap
            
            
              swift
              
              
            
          
          extension Array {
    func tryMap<T>(_ transform: (Element) throws -> T) rethrows -> [T] {
        var result: [T] = []
        for e in self { result.append(try transform(e)) }
        return result
    }
}
let nums = ["1", "2", "A"]
let parsed: [Int] = try nums.tryMap { str in
    guard let v = Int(str) else { throw NSError(domain: "NaN", code: 0) }
    return v
}- 坑位
- rethrows函数内部只能抛出由闭包参数传来的错误,不能自己- throw新错误。
- 如果闭包有多个,只要其中一个会抛,即可标注 rethrows。
@escaping:闭包"逃出"函数生命周期
- 什么是"逃逸"
            
            
              swift
              
              
            
          
          var handlers: [() -> Void] = []
func addHandler(_ handler: () -> Void) {
    handlers.append(handler) // 编译错误:闭包可能稍后调用,必须标记 @escaping
}- 数组把闭包"留住"→ 函数栈已销毁→ 闭包逃出→ 必须加 @escaping。
- 正确写法
            
            
              swift
              
              
            
          
          nonisolated(unsafe) var handlers: [() -> Void] = []
func addHandler(_ handler: @escaping () -> Void) {
    handlers.append(handler)
}- 逃逸闭包的内存管理:循环引用
            
            
              swift
              
              
            
          
          class Request {
    var onSuccess: (() -> Void)?
    func start() {
        onSuccess?()
    }
}
class ViewController {
    let request = Request()
    var name = "VC"
    
    func setup() {
        // 强引用 self → 循环引用
        request.onSuccess = {
            print(self.name)
        }
    }
}解决套路:
- 
[weak self]
- 
[unowned self](生命周期确定时)
- 
用 guard let self else { return }消除可选链
- 
逃逸闭包在异步 API 的典型形态 
            
            
              swift
              
              
            
          
          func loadData(
    from url: String,
    completion: @escaping (Data?) -> Void
) {
    DispatchQueue.global().async {
        let data = try? Data(contentsOf: URL(string: url)!)
        DispatchQueue.main.async {
            completion(data) // 再次逃逸,但编译器已允许
        }
    }
}- 与 @nonescaping(默认)对比
| 特性 | 默认(非逃逸) | @escaping | 
|---|---|---|
| 持有成本 | 低,可栈分配 | 高,必须堆分配 | 
| 捕获策略 | 无需 self.限定 | 必须显式处理 self | 
| 使用场景 | 同步回调 | 异步、存储、延迟调用 | 
4 个关键字组合实战:写个"线程安全且短路"的缓存加载器
            
            
              swift
              
              
            
          
          import SwiftUI
final class ImageCache {
    private var storage: [String: Image] = [:]
    private let queue = DispatchQueue(label: "cache", attributes: .concurrent)
    
    /// 仅当 key 不存在时才调用 `factory` 加载
    func load(
        _ key: String,
        factory: @escaping () throws -> Image
    ) rethrows -> Image {
        // 1. 先读缓存(并发读安全)
        if let cached = queue.sync(execute: { storage[key] }) {
            return cached
        }
        
        // 2. 缓存未命中,加锁写
        return try queue.sync(flags: .barrier) {
            if let cached = storage[key] { return cached } // 双检锁
            let image = try factory()
            storage[key] = image
            return image
        }
    }
}亮点:
- @escaping:工厂闭包可能异步下载,必须逃逸。
- rethrows:工厂抛错,缓存器再抛给调用者;若工厂不抛,调用方可免- try。
- @autoclosure未出现,是因为工厂需要多次调用(双检锁),而 autoclosure 只能一次性求值。
常见面试追问
- 
@autoclosure与"零成本抽象"冲突吗?不冲突。编译器会把包起来的表达式生成匿名闭包,优化级别高时会内联,运行时仍接近零成本。 
- 
rethrows可以标记初始化器吗?可以。 
            
            
              swift
              
              
            
          
             init(_ f: () throws -> Void) rethrows { try f() }- 
逃逸闭包为什么默认捕获强引用? 因为闭包生命周期可能长于当前函数,编译器保守地强引所有外部变量;需要开发者显式 weak/unowned解除循环。