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

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

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

相关推荐
Dream_Ji15 小时前
Swift 入门(一 - 基础语法)
开发语言·ios·swift
HarderCoder20 小时前
async let 也能调度同步函数?——Swift 并发隐藏小技巧详解
swiftui·swift
HarderCoder2 天前
深入理解 SwiftUI 中的 `@Observable` 与 `@Bindable`:从原理到实践
swiftui·swift
大熊猫侯佩4 天前
雪山飞狐之 Swift 6.2 并发秘典:@concurrent 的江湖往事
swiftui·swift·apple
胎粉仔5 天前
Objective-C —— APIs declaration 自定义
ios·objective-c·swift
用户095 天前
Swift Concurrency 中的 Threads 与 Tasks
ios·swiftui·swift
低调小一5 天前
双端 FPS 全景解析:Android 与 iOS 的渲染机制、监控与优化
android·ios·kotlin·swift·fps
用户096 天前
更现代、更安全:Swift Synchronization 框架与 Mutex 锁
ios·面试·swift
大熊猫侯佩9 天前
鹿鼎记豪侠传:Rust 重塑 iOS 江湖(下)
rust·objective-c·swift