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

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

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

相关推荐
Haha_bj1 天前
Swift——高阶函数(map、filter、reduce、forEach、sorted、contains……)
ios·app·swift
妮妮分享1 天前
维智地图如何集成
开发语言·ios·swift
汉秋2 天前
告别 GeometryReader:SwiftUI .visualEffect 实战解析
swiftui·swift
linweidong2 天前
美团ios开发100道面试题及参考答案(下)
objective-c·swift·jspatch·ios开发·ios面试·ios面经·xcode调试
__WanG3 天前
screen time api - FamilyActivityPicker 获取选中应用
ios·iphone·swift
东坡肘子4 天前
Swift、SwiftUI 与 SwiftData:走向成熟的 2025 -- 肘子的 Swift 周报 #116
人工智能·swiftui·swift
大熊猫侯佩5 天前
Swift 6.2 列传(第十三篇):香香公主的“倾城之恋”与优先级飞升
swift·编程语言·apple
1024小神5 天前
Swift配置WKwebview加载网站或静态资源后,开启调试在电脑上debug
swift
kkoral6 天前
基于MS-Swift 为 Qwen3-0.6B-Base 模型搭建可直接调用的 API 服务
python·conda·fastapi·swift
Yorelee.7 天前
ms-swift在训练时遇到的部分问题及解决方案
开发语言·nlp·transformer·swift