dispatch_after 在 Swift 中:GCD 底层原理——通过示例解析

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

dispatch_after 在 Swift 中:GCD 如何底层执行延迟任务



想象一下:你正在用 Swift 编写一个应用程序,需要一段代码不立即执行,而是,比如说,3 秒后执行。你首先想到的可能是 dispatch_after。但如果你查看旧版本的 Swift 或 Stack Overflow,你会发现这个方法已经改变了。关键是,很少有人真正理解,当我们说“2 秒后执行这个”时,Grand Central Dispatch (GCD) 内部到底发生了什么。



在这篇文章中,我们不仅会展示如何在现代 Swift 中编写 dispatch_after。我们还会深入 GCD 的底层,解析队列、线程和定时器,以便你能编写可靠且高效的异步代码。开始吧!



什么是 dispatch_after,它为什么变成了 asyncAfter?



在旧版本的 Swift(1.x, 2.x)中,你可能会看到这样的代码:



dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(2 * NSEC_PER_SEC)), dispatch_get_main_queue()) {    print("2 秒过去了")}


这段代码可以工作,但看起来很笨重。从 Swift 3 开始,Apple 重构了整个 GCD API,使其更加“Swift 化”。现在,同样的功能看起来像这样:



DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {    print("2 秒过去了")}


请注意:asyncAfter 是队列(DispatchQueue)的一个方法,而不是一个独立的函数。这是一个关键的变化:现在你明确指定了在哪个队列上执行任务。



GCD 底层如何工作:队列、线程和定时器



要理解 dispatch_after(或 asyncAfter),你需要了解三个实体:



  • DispatchQueue — 任务队列。可以是串行的(serial)或并发的(concurrent)。
  • DispatchWorkItem — 你放入队列的任务。在我们的例子中,它是一个闭包。
  • DispatchTime — 任务应该启动的时间点。GCD 使用系统定时器来跟踪截止时间。


当你调用 asyncAfter 时,GCD 并不会创建一个新线程。相反,它会:



  1. 创建一个定时器,该定时器会在指定时间触发。
  2. 将任务放入一个特殊的延迟任务队列(通常称为 dispatch timer)。
  3. 当定时器触发时,GCD 将任务移动到常规的 DispatchQueue 中,在那里它等待分配线程。


如果你放入任务的队列正忙于处理其他任务,你的延迟任务可能会在指定的截止时间之后才执行。这是一个重要的点:dispatch_after 保证任务不会在截止时间之前执行,但可能会在之后执行。



实践示例:用于更新 UI 的 dispatch_after



最常见的场景是在用户操作后一段时间更新界面。假设你显示一条“已保存”的消息,并在 2 秒后将其移除:



func showTemporaryMessage() {    let messageLabel = UILabel()    messageLabel.text = "已保存!"    messageLabel.alpha = 1.0    view.addSubview(messageLabel)

DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { UIView.animate(withDuration: 0.5) { messageLabel.alpha = 0.0 } completion: { _ in messageLabel.removeFromSuperview() } }}


请注意:我们使用了 DispatchQueue.main,因为所有 UI 操作都必须在主线程上执行。如果我们在后台队列上调用 asyncAfter,然后尝试修改 UI,应用程序会崩溃。



dispatch_after 的陷阱:内存泄漏和 self 捕获



最常见的错误之一是在闭包内对 self 的强引用

博客

书籍推荐