Swift 函数完全指南(三):`@autoclosure`、`rethrows`、`@escaping` 与内存管理

@autoclosure:把"表达式"包成"闭包",实现"短路求值"

  1. 场景回顾
swift 复制代码
/// 自己写的 assert(简化版)
func myAssert(_ condition: Bool, _ message: String) {
    if !condition { print("❌ \(message)") }
}
myAssert(2 > 3, "2 不可能大于 3")   // 无论断言是否成功,message 都会被求值

问题:

  • 字符串先拼接完成,再传进函数------性能浪费。
  • 若拼接代价高("计算成本:\(expensive())"),则每次调用都白算。
  1. @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 的语法糖核心。
  1. 官方用法对照
  • assert(condition:)
  • fatalError(message:)
  • os_logmessage 参数

rethrows:让"函数参数"的异常传播出去

  1. 背景
swift 复制代码
func map<T>( _ array: [Int], _ transform: (Int) throws -> T) rethrows -> [T] {
    try array.map(transform)
}
  • 如果 transform 不抛错,map 也不会抛;
  • 如果 transform 抛错,map 再把异常原路抛回给调用者。
  1. throws 的区别
关键字 谁可能抛错 调用方必须用 try
throws 函数本身
rethrows 仅闭包参数 ✅(但闭包不抛就隐式免 try
  1. 实现 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
}
  1. 坑位
  • rethrows 函数内部只能抛出由闭包参数传来的错误,不能自己 throw 新错误。
  • 如果闭包有多个,只要其中一个会抛,即可标注 rethrows

@escaping:闭包"逃出"函数生命周期

  1. 什么是"逃逸"
swift 复制代码
var handlers: [() -> Void] = []

func addHandler(_ handler: () -> Void) {
    handlers.append(handler) // 编译错误:闭包可能稍后调用,必须标记 @escaping
}
  • 数组把闭包"留住"→ 函数栈已销毁→ 闭包逃出→ 必须加 @escaping
  1. 正确写法
swift 复制代码
nonisolated(unsafe) var handlers: [() -> Void] = []

func addHandler(_ handler: @escaping () -> Void) {
    handlers.append(handler)
}
  1. 逃逸闭包的内存管理:循环引用
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)
        }
    }
}

解决套路:

  1. [weak self]

  2. [unowned self](生命周期确定时)

  3. guard let self else { return } 消除可选链

  4. 逃逸闭包在异步 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) // 再次逃逸,但编译器已允许
        }
    }
}
  1. @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 只能一次性求值。

常见面试追问

  1. @autoclosure 与"零成本抽象"冲突吗?

    不冲突。编译器会把包起来的表达式生成匿名闭包,优化级别高时会内联,运行时仍接近零成本。

  2. rethrows 可以标记初始化器吗?

    可以。

swift 复制代码
   init(_ f: () throws -> Void) rethrows { try f() }
  1. 逃逸闭包为什么默认捕获强引用?

    因为闭包生命周期可能长于当前函数,编译器保守地强引所有外部变量;需要开发者显式 weak/unowned 解除循环。

相关推荐
HarderCoder5 小时前
Swift 函数完全指南(二):泛型函数与可变参数、函数重载、递归、以及函数式编程思想
swift
HarderCoder6 小时前
Swift 函数完全指南(一)——从入门到嵌套
swift
jh_cao15 小时前
(4)SwiftUI 基础(第四篇)
ios·swiftui·swift
progalchemist1 天前
Quick SwiftObjective-C测试框架入门教程
开发语言·其他·objective-c·swift
HarderCoder1 天前
Swift 闭包(Closure)从入门到深入:语法、捕获与实战
swift
HarderCoder1 天前
Swift 集合类型详解(三):自定义集合、持久化结构与 ORM 共舞
swift
HarderCoder1 天前
Swift 集合类型详解(一):Array、Set、Dictionary 全貌与选型思路
swift
HarderCoder1 天前
Swift 集合类型详解(二):自定义 Hashable、值语义与性能陷阱
swift
东坡肘子2 天前
Sora 2:好模型,但未必是好生意 | 肘子的 Swift 周报 #0105
人工智能·swiftui·swift