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 并不会创建一个新线程。相反,它会:
- 创建一个定时器,该定时器会在指定时间触发。
- 将任务放入一个特殊的延迟任务队列(通常称为 dispatch timer)。
- 当定时器触发时,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 的强引用