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

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

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

相关推荐
tangweiguo0305198710 小时前
SwiftUI布局完全指南:从入门到精通
ios·swift
用户794572239541317 小时前
【RxSwift】Swift 版 ReactiveX,响应式编程优雅处理异步事件流
swift·rxswift
战族狼魂1 天前
XCode 发起视频 和 收到视频通话邀请实现双语功能 中文和俄语
swift
UXbot1 天前
2026年AI全链路产品开发工具对比:5款从创意到上线一站式平台深度解析
前端·ui·kotlin·软件构建·swift·原型模式
报错小能手1 天前
ios开发方向——swift并发进阶核心 @MainActor 与 DispatchQueue.main 解析
开发语言·ios·swift
报错小能手3 天前
ios开发方向——swift并发进阶核心 async/await 详解
开发语言·ios·swift
用户79457223954133 天前
【Lottie】让设计稿上的动效直接"活"在 App 里
swiftui·swift
Mr_Tony5 天前
Swift 中的 Combine 框架完整指南(含示例代码 + 实战)
开发语言·swift
用户79457223954135 天前
【SnapKit】优雅的 Swift Auto Layout DSL 库
swiftui·swift
报错小能手5 天前
ios开发方向——swift内存基础
开发语言·ios·swift