用 `defer` 管理异步清理:Swift 中的“保险丝”模式

参考原文:Using defer in Swift to manage state cleanup

异步状态清理的典型痛点

swift 复制代码
func fetch() async {
    isLoading = true
    do {
        articles = try await service.fetchArticles()
        isLoading = false          // ✅ 正常路径
    } catch {
        self.error = error.localizedDescription
        isLoading = false          // ✅ 异常路径
    }
}

问题:

  • 函数变大后,多处 return / throw 容易漏掉 isLoading = false
  • Task 取消也会提前退出,清理代码同样不会执行
  • 复制粘贴"收尾"逻辑 = 维护噩梦

官方保险丝:defer

swift 复制代码
func fetch() async {
    isLoading = true
    defer { isLoading = false }    // 1️⃣ 只写一次,**无论怎么退出**都会执行
    
    do {
        articles = try await service.fetchArticles()
    } catch {
        self.error = error.localizedDescription
        // 不需要再写 isLoading = false
    }
}

defer 块在作用域结束时(正常、throw、early-return、Task.cancel)必定调用。

defer 的 4 种退出场景全覆盖

退出方式 defer 是否执行 说明
正常跑到函数尾 顺序执行
throw 错误 在错误传播前执行
early return return之后、真正退出前执行
Task 取消 await抛出 CancellationError后仍执行

实战 1:Early-Return 也不漏

swift 复制代码
func validateAndSave(_ text: String) async throws {
    defer { isLoading = false }
    isLoading = true
    
    guard !text.isEmpty else { return }   // ✅ defer 仍会执行
    guard text.count <= 280 else { return }
    
    try await api.save(text)
}

实战 2:多 defer 顺序(栈式)

swift 复制代码
func processFile() async throws {
    let handle = FileHandle(forReadingFrom: url)
    defer { try? handle.close() }          // 2️⃣ 后注册:先执行
    
    let buffer = malloc(4096)
    defer { free(buffer) }                 // 1️⃣ 先注册:后执行
    
    // ... 真正逻辑
}
// 退出时:free → close(**LIFO 栈顺序**)

实战 3:异步网络层统一清理

swift 复制代码
final class ListViewModel: ObservableObject {
    @Published private(set) var items: [Item] = []
    @Published var isLoading = false
    @Published var error: String?
    
    func reload() async {
        isLoading = true
        defer { isLoading = false }          // 一次写好,终身受益
        
        do {
            items = try await service.fetch()
            error = nil
        } catch {
            if error is URLError {
                self.error = "网络似乎断开了"
            } else {
                self.error = error.localizedDescription
            }
            // 无需再管 isLoading
        }
    }
}

常见误区

误区 正确做法
defer写在 do-catch内部 写在整个函数作用域顶部,确保任何退出路径都能覆盖
依赖返回值做清理 defer无法访问函数返回值,用临时变量 + defer 组合
defer里再 throw 会覆盖原错误,清理代码应保证永不 throw

一句话总结

defer 是 Swift 的"保险丝":声明一次,必定执行,

让异步清理、解锁、关闭文件、重置状态不再被遗漏。

把它写在作用域最顶部,然后忘了它------它会替你收尾。

相关推荐
HarderCoder3 小时前
Swift 协议(Protocol)指南(四):协议扩展(Protocol Extension)——让“协议”自己也有默认实现
swift
HarderCoder4 小时前
Swift 协议(Protocol)指南(三):Primary Associated Type、some/any 与泛型式协议实战
swift
HarderCoder4 小时前
Swift 协议(Protocol)指南(二):关联类型、Self 约束与泛型递归,一次彻底搞懂
swift
HarderCoder4 小时前
Swift 协议(Protocol)指南(一):从语法到实战
swift
HarderCoder4 小时前
Swift TaskGroup 结果顺序踩坑指南:为什么返回顺序和创建顺序不一致,以及最通用的修复办法
swift
Swift社区12 小时前
iOS 基于 Foundation Model 构建媒体流
ios·iphone·swift·媒体
大熊猫侯佩1 天前
侠客行・iOS 26 Liquid Glass TabBar 破阵记
ios·swiftui·swift
qixingchao2 天前
iOS SwiftUI 动画开发指南
ios·swiftui·swift
大熊猫侯佩2 天前
猿族代码战记:Mutex 升级版——守护 Swift 并发的“香蕉仓库”
swiftui·swift·apple
大熊猫侯佩2 天前
Thread.sleep 与 Task.sleep 终极对决:Swift 并发世界的 “魔法休眠术” 揭秘
ios·swift·apple