求求你,别在 Swift 协程开头写 guard let self = self 了!

别让 guard let self = self 锁死你的控制器:Swift 协程内存"绑架"大揭秘 🚀

01. 一个"灵异"的 deinit

你是否遇到过这种场景:在 ViewController 里的 Task 启动了一个长达 10 秒的图片上传任务。用户觉得太慢,点击"返回"退出了页面。按照 ARC 的逻辑,VC 应该立即销毁。

但控制台却一片寂静。直到 10 秒后上传完成,那个 deinit 才像"诈尸"一样蹦出来。

真相是:你的 ViewController 被"绑架"了,而绑匪正是你亲手写的 guard let self = self


02. 硬核拆解:Task 到底在堆里做了什么?

要理解这个现象,我们得聊聊 Swift 协程的底层存储机制:异步帧(Async Frame)

传统的栈(Stack)

普通函数执行时,变量存在上。函数跑完,栈帧弹出,变量销毁。

协程的堆(Heap)

Task 是为了 await 设计的。await 的本质是 "挂起(Suspension)"

  • 挂起瞬间 :当代码运行到 await,当前线程会被释放去干别的事。为了保证 await 回来后代码能接着跑,Swift 必须把当前函数的所有状态(包括你的强引用 self从栈上拷贝到堆(Heap)上

结论: 只要这个 Task 没运行到最后那个大括号 },堆上的空间就不会销毁。你提前 guard 出来的强引用 self 就像一把锁,把 VC 死死锁在堆里。


03. 别做"暴力解包"的搬运工 🧱

很多开发者习惯在 Task 开头就"暴力解包":

Swift

swift 复制代码
// ❌ 错误示范:VC 将被锁死直到任务结束
Task { [weak self] in
    guard let self = self else { return } // 1. 这里强行"弱转强"
    print("⏳ 上传中...")
    let url = await logic.uploadImage() // 2. 协程挂起,强引用 self 存入堆中
    self.show(url)
}

此时的引用链:

Task (堆内存) ➔ 局部变量 self (强引用) ➔ ViewController

即便用户关了页面,Task 不结束,VC 就释放不了。这就是 "内存延迟释放"


04. 满分范本:后置解包法 💡

我们要做的其实很简单:让任务先跑,人若还在,再干活。

Swift

swift 复制代码
// ✅ 工业级安全模版
Task { [weak self] in
    print("⏳ 任务发起,此时 VC 自由了")
    
    // 关键:不提前 guard,直接 await 发起异步
    let url = await self?.logic.uploadImage() 
    
    // 任务回来了,此时再看"人还在不在"
    guard let self = self else { 
        print("🛑 页面已关,逻辑优雅退出")
        return 
    }
    
    self.show(url) // 只有在这里,才产生瞬时的强引用
}
  • 收益 :用户关掉页面,VC 引用计数清零,立刻执行 deinit

05. 进阶大师:不仅省内存,还要省流量 📡

既然观众离场了,戏台确实不该演了。对于耗时极长的任务,我们应该在 deinit 时主动取消。

Swift

swift 复制代码
class MasterVC: UIViewController {
    private var loadTask: Task<Void, Never>?

    func start() {
        loadTask?.cancel()
        loadTask = Task { [weak self] in
            let res = await logic.longTimeRequest()
            guard let self = self, !Task.isCancelled else { return }
            self.done(res)
        }
    }

    deinit {
        loadTask?.cancel() // 页面关,网络停,流量省
        print("✅ 完美释放!")
    }
}

总结:我的避坑座右铭

  1. 挂起前(await)别解包 :别在网络回来前,用 guard 把 VC 锁死。
  2. 挂起时用可选链self?.logic.request() 才是真正的弱引用。
  3. 回来后再检查:任务结束时,确认 VC 还在,再刷新 UI。

记住:ViewController 是"消耗品",别让它成为你内存里的"传家宝"。


希望这篇文章能帮你理清 Swift 协程的引用谜团!如果你也曾被 deinit 延迟困扰过,欢迎在评论区分享你的经历。👇

下一步建议: 你可以去检查一下项目中那些处理大图上传或复杂计算的 Task,把 guard 后移,你会发现 App 的响应速度和内存表现会有质的飞跃。

相关推荐
梦帮科技32 分钟前
Node.js配置生成器CLI工具开发实战
前端·人工智能·windows·前端框架·node.js·json
VT.馒头1 小时前
【力扣】2695. 包装数组
前端·javascript·算法·leetcode·职场和发展·typescript
css趣多多1 小时前
一个UI内置组件el-scrollbar
前端·javascript·vue.js
C澒2 小时前
前端整洁架构(Clean Architecture)实战解析:从理论到 Todo 项目落地
前端·架构·系统架构·前端框架
C澒2 小时前
Remesh 框架详解:基于 CQRS 的前端领域驱动设计方案
前端·架构·前端框架·状态模式
Charlie_lll2 小时前
学习Three.js–雪花
前端·three.js
onebyte8bits2 小时前
前端国际化(i18n)体系设计与工程化落地
前端·国际化·i18n·工程化
C澒2 小时前
前端分层架构实战:DDD 与 Clean Architecture 在大型业务系统中的落地路径与项目实践
前端·架构·系统架构·前端框架
BestSongC2 小时前
行人摔倒检测系统 - 前端文档(1)
前端·人工智能·目标检测
0思必得03 小时前
[Web自动化] Selenium处理滚动条
前端·爬虫·python·selenium·自动化