Swift中如何失效DispatchWorkItem?录制计时器叠加问题咨询
问题分析与解决方案
你遇到的核心问题是:DispatchWorkItem的cancel()方法并不会主动中断正在执行的代码——它只是给这个work item打上「已取消」的标记,代码会继续执行,直到你主动检查这个标记并退出逻辑。这就是为什么你停止录制后旧的计时器还在跑,重启后又新增一个,最终导致叠加的原因。
为什么你的初始代码没用?
当你调用recordedTimeQueue?.cancel()时,那个正在后台跑的for循环并没有收到「停止」的指令,它会继续循环600次,每次更新文本。而你每次重启录制都会新建一个DispatchWorkItem,旧的还在后台运行,自然就出现了多个计时器同时更新的情况。
正确的解决方法
你编辑里的思路是对的——必须在循环内部检查work item的isCancelled状态,主动跳出循环。不过可以优化一下代码的安全性和可读性:
优化后的代码示例
// 声明全局/类级别的可选DispatchWorkItem var recordedTimeQueue: DispatchWorkItem? // 启动录制计时器的方法 func startRecordingTimer() { // 启动前先清理旧的计时器,避免叠加 recordedTimeQueue?.cancel() recordedTimeQueue = nil recordedTimeQueue = DispatchWorkItem { [weak self] in guard let self = self else { return } // 避免循环引用,防止内存泄漏 for i in 1..<600 { // 检查是否被取消,用可选绑定避免强制解包崩溃 guard !(self.recordedTimeQueue?.isCancelled ?? true) else { break } // 用Swift原生的sleep方法替代usleep,可读性更好 Thread.sleep(forTimeInterval: 1) DispatchQueue.main.async { self.recordedTime?.text = "\(i)" } } } // 执行work item DispatchQueue.global().async(execute: recordedTimeQueue!) } // 停止录制时调用的方法 func stopRecording() { print("recording stopped") recordedTimeQueue?.cancel() recordedTimeQueue = nil // 彻底清空,确保下次启动是全新的计时器 }
关键优化点说明
- 启动前清理旧计时器:每次启动新计时器前,先取消并清空旧的
recordedTimeQueue,从根源避免叠加问题。 - 安全检查
isCancelled:用?? true做默认值,即使recordedTimeQueue已经被置为nil,也能安全退出循环,不会因为强制解包(!)导致崩溃。 - 避免循环引用:在work item里用
[weak self]和可选绑定guard let self = self,防止因为work item持有self导致的内存泄漏。 - 更优雅的延时:用
Thread.sleep(forTimeInterval: 1)替代usleep(1000000),两者都是阻塞线程1秒,但前者是Swift原生API,可读性更强。
额外补充:为什么必须自己检查取消状态?
GCD设计成这样是为了线程安全——强制中断正在执行的代码可能会导致资源泄漏、数据不一致等问题。所以它只提供「标记取消」的机制,让开发者自己在合适的时机(比如循环的每次迭代)检查状态并主动退出,这样能保证代码的可控性和安全性。
内容的提问来源于stack exchange,提问作者Chewie The Chorkie
相关产品推荐
相关产品推荐

