在 iOS 中等待 50 运行 循环滴答

Wait 50 run loop ticks in iOS

我想等待 运行 循环到 运行 并且屏幕渲染 50 次后再执行操作。

是否有必要为此使用 CAMediaTiming 和计数器?有没有办法直接连接到 NSRunLoop?我可以像这样使用 50 个嵌套 DispatchQueue.async 调用来实现吗?

import Dispatch

func wait(ticks: UInt, queue: DispatchQueue = DispatchQueue.main, _ handler: @escaping () -> Void) {
    var ticks = ticks

    func predicate() {
        queue.async {
            ticks -= 1
            if ticks < 1 {
                handler()
                return
            }
            queue.async(execute: predicate)
        }
    }

    predicate()
}

编辑:如果有人想知道,该代码段确实有效,并且在我们谈论应用程序 运行 循环时表现非常好。

您可以使用 CADisplayLink,它会在每次屏幕更新时调用。

let displayLink = CADisplayLink(target: self, selector: #selector(drawFunction(displayLink:)))
displayLink.add(to: .main, forMode: .commonModes)

在 drawFunction(或 predicate 等)中,我们可以从 ticks 中减去。当它们达到 0 时,我们已经达到第 50 帧并使 displayLink 无效。

var ticks = 50
@objc private func drawFunction(displayLink: CADisplayLink) {
    doSomething()
    ticks -= 1
    if ticks == 0 {
        displayLink.invalidate()
        displayLink = nil
        return
    }
}

CADisplayLink 还可以提供帧之间的时间量。可以找到类似的讨论here。如果您关心这里的绝对准确性,您可以计算帧之间的时间。来自文档:

The duration property provides the amount of time between frames at the maximumFramesPerSecond. To calculate the actual frame duration, use targetTimestamp - timestamp. You can use this value in your application to calculate the frame rate of the display, the approximate time that the next frame will be displayed, and to adjust the drawing behavior so that the next frame is prepared in time to be displayed.

我认为使用 CADisplayLink 而不是 Dispatch 是更好的解决方案。这是一个例子:

    class Waiter {
        let handler: () -> Void
        let ticksLimit: UInt
        var ticks: UInt = 0
        var displayLink: CADisplayLink?

        init(ticks: UInt, handler: @escaping () -> Void) {
            self.handler = handler
            self.ticksLimit = ticks
        }

        func wait() {
            createDisplayLink()
        }

        private func createDisplayLink() {
            displayLink = CADisplayLink(target: self,
                                      selector: #selector(step))
            displayLink?.add(to: .current,
                        forMode: RunLoop.Mode.default)
        }

        @objc private func step(displaylink: CADisplayLink) {
            print(ticks)
            if ticks >= ticksLimit {
                displayLink?.invalidate()
                handler()
            }
            ticks += 1
        }
    }

这是一个用法示例:

    let waiter = Waiter(ticks: 50) {
        print("Handled")
    }
    waiter.wait()