ios DispatchQueue是否可用于对用户输入进行排队?

6jygbczu  于 2023-03-20  发布在  iOS
关注(0)|答案(1)|浏览(106)

在我的游戏中,每次用户向某个特定方向滑动时,屏幕上的某个东西都会动画化。由于每个动画大约需要一秒钟,所以如果用户快速滑动屏幕,我需要一种方法来“记录”滑动将触发的所有函数,并让它们在不断完成时串行执行。我认为队列非常适合这一点,在实现我自己的队列之前,我遇到了DispatchQueue。这让我想知道是否有一种方法可以将DispatchQueue与组和工作项结合使用,以便对函数进行排队,并使它们在完成时一个接一个地执行。
请让我知道DispatchQueue是否可以用这种方式对函数进行排队。
我已经了解了.userInteractivity全局队列,并且假设需要在那里做一些事情;我只是不知道是什么也不知道怎么做。
谢谢。

eufgjt7s

eufgjt7s1#

尽管DispatchQueue看起来很吸引人,但它并不是您的用例的理想工具。
调度队列是一种非常特殊的队列类型,一个以FIFO方式执行的代码块队列。不幸的是,调度队列在管理工作项之间的依赖关系方面并不十分优雅,这些依赖关系本身就是异步的。而且不用说,您的动画是异步的。
诚然,有一些(笨拙的)技术可以使GCD工作,但它不是最适合您的用例的解决方案。操作队列处理异步任务之间的依赖关系稍好一些,但不必要地复杂(需要创建定制的异步Operation子类,其中包含isExecutingisFinished等所有适当的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视频Meet async/await in Swift
  • 在Swift并发中,我会使用“异步序列”模式来管理一系列异步任务,请参见WWDC 2021视频Meet AsyncSequence
  • 更具体地说,我将使用特定类型的AsyncSequence,即AsyncChannel(来自Swift Async Algorithms库)来提供一个异步序列,在该序列中,用户输入可以轻松地向现有序列添加新项。
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 concurrency,那么在深入研究AsyncChannel方法之前,您可能需要先熟悉它,但是如果您正在寻找处理一系列异步任务的最符合逻辑的模式,那么AsyncChannel是值得考虑的。

相关问题