在我的游戏中,每当用户向特定方向滑动时,屏幕上的某些内容就会出现动画。由于每个动画大约需要一秒钟,如果用户在屏幕上快速滑动,我需要一种方法来“记录”滑动将触发的所有功能,并让它们在完成时连续执行。我认为队列对此来说是完美的,在实现我自己的队列之前,我遇到了
DispatchQueue
、DispatchGroup
和DispatchWorkItem
。这让我想知道是否有一种方法可以将 DispatchQueue
与组和工作项结合使用来排队函数并让它们在完成时一个一个地执行。
如果
DispatchQueue
可以用这种方式排队函数,请告诉我。
我已经知道
.userInteractivity
全局队列,并且假设需要在那里做一些事情;我只是不知道什么也不知道如何。
谢谢。
尽管看起来很有吸引力,
DispatchQueue
并不是您用例的理想工具。
调度队列是一种非常特殊的队列,以先进先出的方式执行的代码块队列。不幸的是,调度队列在管理本身是异步的工作项之间的依赖关系方面并不是非常优雅。不用说,你的动画是异步的。
诚然,有一些(笨拙的)技术可以使 GCD 工作,但这不是您的用例的最自然的解决方案。操作队列更好地处理异步任务之间的依赖关系,但不必要地复杂(需要创建自定义异步
Operation
子类,其中包含用于 isExecuting
、isFinished
等的所有适当的 KVO 通知)。
正如您所建议的,一个非常简单的解决方案是拥有一个您自己的捕获用户交互的模型对象的 FIFO 队列:
Array
;append
对该数组的值;removeFirst
该数组中的一个值;和例如,在下面,我有一个
points
的队列/数组,代表用户在 UI 中点击的位置,并以 FIFO 方式为视图的位置设置动画。 (为了保持代码片段简单,我在 IB 中创建了动画视图和点击手势识别器。)
import UIKit
class SimpleViewController: UIViewController {
private var points: [CGPoint] = []
private var isRunning = false
@IBOutlet weak var animatedView: UIView!
@IBAction func handleTap(_ gesture: UITapGestureRecognizer) {
let point = gesture.location(in: view)
points.append(point)
moveNext()
}
func moveNext() {
guard !points.isEmpty, !isRunning else {
return
}
isRunning = true
let point = points.removeFirst()
UIView.animate(withDuration: 1) { [self] in
animatedView.center = point
} completion: { [self] isFinished in
isRunning = false
if isFinished { moveNext() }
}
}
}
这里的细节不是很相关。基本思想是可以使用
Array
作为用户输入的 FIFO 队列。
如果您打算使用一些框架对象来管理您的队列,您可以考虑以下方法,完全跳过调度/操作队列替代方案。
相反,我会直接跳到 Swift 并发,使用
async
-await
,优雅地处理异步任务之间的依赖关系。请参阅 WWDC 2021 视频Swift 中的异步/等待。
在 Swift 并发性中,我会使用“异步序列”模式来管理一系列异步任务。请参阅 WWDC 2021 视频认识 AsyncSequence。
更具体地说,我会使用特定类型的
AsyncSequence
,即 AsyncChannel
(来自 Swift 异步算法 库)来提供一个异步序列,您的用户输入可以在其中轻松添加新的现有序列的项目。
import UIKit
import AsyncAlgorithms // https://github.com/apple/swift-async-algorithms
class ChannelViewController: UIViewController {
private let pointChannel = AsyncChannel<CGPoint>()
private var channelTask: Task<Void, Never>?
@IBOutlet weak var animatedView: UIView!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
startChannel()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
stopChannel()
}
@IBAction func handleTap(_ gesture: UITapGestureRecognizer) {
let point = gesture.location(in: view)
Task {
await pointChannel.send(point)
}
}
func startChannel() {
// in case we ever accidentally call this twice
stopChannel()
// create task …
channelTask = Task {
// … that iterates through `CGPoint` sent on the channel
for await point in pointChannel {
await move(to: point)
}
}
}
func stopChannel() {
channelTask?.cancel()
channelTask = nil
}
func move(to point: CGPoint) async {
await withCheckedContinuation { continuation in
UIView.animate(withDuration: 1) { [self] in
animatedView.center = point
} completion: { _ in
continuation.resume()
}
}
}
}
如果您不熟悉 Swift 并发,您可能希望在深入研究这种
AsyncChannel
方法之前熟悉它。但是,如果您正在寻找处理一系列异步任务的最合乎逻辑的模式,那么AsyncChannel
值得考虑。