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
相关产品推荐
相关产品推荐

