You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift中如何在deinit时取消DispatchQueue.main.asyncAfter任务

解决DispatchQueue.main.asyncAfter延迟任务未取消导致的野指针问题

这个问题我之前也踩过坑——视图控制器被弹出、子视图明明已经销毁了,之前提交的5秒延迟任务居然还会执行,访问已经释放的subView直接崩溃,太闹心了!

核心原因很明确:DispatchQueue.main.asyncAfter提交的任务会被队列持有,哪怕对应的对象已经走完deinit流程,只要任务还没到执行时间,就会一直待在队列里;等时间到了执行闭包时,闭包里捕获的self已经变成野指针,访问它的属性自然就触发报错了。

下面是最稳妥的解决方案,用DispatchWorkItem来管理延迟任务,让我们能在对象销毁时主动取消它:


第一步:添加任务句柄属性

在你的subView.swift里,先定义一个DispatchWorkItem类型的可选属性,用来保存我们的延迟任务:

private var delayedWorkItem: DispatchWorkItem?

第二步:用WorkItem封装延迟任务

把原来直接写在asyncAfter里的闭包,改成用DispatchWorkItem来封装,同时一定要用[weak self]避免强引用(防止对象因为闭包的强持有无法被及时销毁):

// 创建任务,封装要执行的逻辑
delayedWorkItem = DispatchWorkItem { [weak self] in
    self?.doSomething()
}

// 提交延迟任务到主队列
if let workItem = delayedWorkItem {
    DispatchQueue.main.asyncAfter(deadline: .now() + 5.0, execute: workItem)
}

第三步:在deinit中取消任务

当对象要被销毁时,主动取消这个延迟任务,从根源上阻止它后续执行:

deinit {
    // 取消任务:如果任务还没开始执行,就不会再触发了
    delayedWorkItem?.cancel()
    // 置空属性,避免残留引用
    delayedWorkItem = nil
    print("subView 已销毁,延迟任务已取消")
}

第四步:优化doSomething方法

为了双重保险,在doSomething里先做安全校验,确保self和subView都处于可用状态:

func doSomething() {
    // 先校验self是否存在,再检查subView的状态
    guard let self = self, !self.subView.flgA else {
        return
    }
    // 这里写原来的业务逻辑
}

额外补充说明

  • DispatchWorkItem.cancel()的作用:如果任务还没开始执行,会直接取消;如果任务已经在执行中,你可以在任务逻辑里通过workItem.isCancelled来判断是否要终止后续操作(比如循环里的中断判断)
  • 为什么必须用DispatchWorkItem?因为原生的asyncAfter没有提供直接取消任务的API,只有通过持有WorkItem才能拿到任务句柄进行取消操作
  • 关于[weak self]:虽然你提到对象已经执行了deinit,但如果闭包强持有self,可能会导致对象无法被及时销毁,形成内存泄漏,加上弱引用能从根源避免这类问题

内容的提问来源于stack exchange,提问作者K.K.D

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 06:35:30