如何等待DispatchWorkItem执行完成且保证变量仅主线程可访问?
解决DispatchWorkItem等待与主线程变量访问的问题
嘿,你现在用轮询的方式确实有点“笨办法”的味道,不仅浪费资源,还容易因为线程安全问题出bug。结合你要求所有变量仅能被主线程访问的核心需求,我给你两个优雅的实现方案,既解决等待问题,又严格保证线程安全:
方案一:用DispatchGroup跟踪任务(推荐)
DispatchGroup是Apple官方推荐的跟踪异步任务完成的工具,内核级别的等待比轮询高效太多,而且我们可以轻松把变量的读写限制在主线程:
第一步:初始化变量与任务组
把需要保护的信息变量和DispatchGroup都放在ViewController里,任务启动时标记“进入组”,任务完成回到主线程更新变量后标记“离开组”:
class YourViewController: UIViewController { // 仅主线程读写的信息变量 private var fetchedInfo: String? // 跟踪异步任务的DispatchGroup private let taskGroup = DispatchGroup() override func viewDidLoad() { super.viewDidLoad() // 标记任务开始 taskGroup.enter() DispatchQueue.global().async { [weak self] in // 模拟耗时的信息获取操作(比如网络请求、本地数据解析) Thread.sleep(forTimeInterval: 3) let newInfo = "从全局队列拿到的信息" // 必须回到主线程更新变量,同时标记任务完成 DispatchQueue.main.async { self?.fetchedInfo = newInfo self?.taskGroup.leave() } } } }
第二步:按钮点击时等待任务完成
在按钮点击事件里,调用taskGroup.wait()等待任务结束,同时因为变量的读写都在主线程,完全符合你的要求:
@IBAction func showInfoButtonTapped(_ sender: UIButton) { // 设置10秒超时,避免主线程无限阻塞 let waitResult = taskGroup.wait(timeout: .now() + 10) switch waitResult { case .success: // 任务完成,直接在主线程访问变量 if let info = fetchedInfo { print("展示信息:\(info)") // 这里可以做UI操作,比如更新label、弹出alert } case .timedOut: print("等待超时,信息还没获取完") } }
方案二:用DispatchSemaphore实现等待
如果你对信号量更熟悉,也可以用DispatchSemaphore实现同样的逻辑,同样保证变量仅主线程访问:
class YourViewController: UIViewController { private var fetchedInfo: String? // 初始化信号量,初始值为0(表示没有可用资源) private let semaphore = DispatchSemaphore(value: 0) override func viewDidLoad() { super.viewDidLoad() DispatchQueue.global().async { [weak self] in // 模拟耗时操作 Thread.sleep(forTimeInterval: 3) let newInfo = "拿到的信息" // 主线程更新变量,然后发送信号 DispatchQueue.main.async { self?.fetchedInfo = newInfo self?.semaphore.signal() // 信号量+1,通知等待的线程 } } } @IBAction func showInfoButtonTapped(_ sender: UIButton) { // 等待信号,超时10秒 let waitResult = semaphore.wait(timeout: .now() + 10) if waitResult == .success, let info = fetchedInfo { print("展示信息:\(info)") } else { print("等太久了,放弃") } } }
关键注意点
- 别让主线程堵太久:如果你的异步任务耗时超过1秒,主线程阻塞会导致UI卡顿,这种情况下建议改成“任务完成后自动启用按钮”的方式,用户点击时直接展示已准备好的信息,体验更好。
- 弱引用避免内存泄漏:异步闭包里一定要用
[weak self],不然ViewController会因为被闭包强引用而无法释放。
内容的提问来源于stack exchange,提问作者MrGreen
相关产品推荐
相关产品推荐

