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 解除循环。

相关推荐
Swift社区9 小时前
LeetCode 409 - 最长回文串 | Swift 实战题解
算法·leetcode·swift
YGGP3 天前
【Swift】LeetCode 54. 螺旋矩阵
swift
Swift社区3 天前
Foundation Model 在 Swift 中的类型安全生成实践
开发语言·安全·swift
HarderCoder3 天前
【Swift 可选链】从“如果存在就点下去”到“安全穿隧”到空合运算符
swift
HarderCoder3 天前
Swift 反初始化器详解——在实例永远“消失”之前,把该做的事做完
swift
HarderCoder3 天前
Swift 并发编程新选择:Mutex 保护可变状态实战解析
swift
HarderCoder4 天前
Swift 模式:解构与匹配的安全之道
swift
东坡肘子4 天前
Swift 官方发布 Android SDK | 肘子的 Swift 周报 #0108
android·swiftui·swift
YGGP5 天前
【Swift】LeetCode 53. 最大子数组和
swift
2501_916008895 天前
用多工具组合把 iOS 混淆做成可复用的工程能力(iOS混淆|IPA加固|无源码混淆|Ipa Guard|Swift Shield)
android·开发语言·ios·小程序·uni-app·iphone·swift