用 `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 的"保险丝":声明一次,必定执行,

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

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

相关推荐
大熊猫侯佩1 天前
冰火岛 Tech 传:Apple Foundation Models 心法解密(上集)
llm·ai编程·swift
HarderCoder1 天前
深入理解 SwiftUI 的 Structural Identity:为什么“换个条件分支”就会丢状态?
swiftui·swift
HarderCoder1 天前
Swift Continuations 完全指南:一口气弄懂 4 种“桥梁”
swift
HarderCoder1 天前
Swift 的 `withoutActuallyEscaping`:借一个 `@escaping` 身份,但不真的逃跑
swift
Swift社区1 天前
Swift 解法详解:LeetCode 371《两整数之和》
开发语言·leetcode·swift
Swift社区1 天前
Swift 解法详解 LeetCode 362:敲击计数器,让数据统计更高效
开发语言·leetcode·swift
HarderCoder1 天前
Opaque Types 完全指南:Swift 的“密封盒子”魔法
swift
HarderCoder1 天前
Thread.sleep vs Task.sleep:一句话记住“别再阻塞线程”
swift
YungFan1 天前
Swift 6.2 新特性
swiftui·swift