为什么减少间隔不会加速 iOS 计时器执行?

Why is decreasing interval not speeding up iOS timer execution?

当我 运行 这个计时器代码为 60 秒 duration/1 秒间隔或 6 秒/.1 秒间隔时,它按预期工作(完成速度快 10 倍)。但是,将值减少到 0.6 秒/.01 秒并不会像预期的那样加快整体操作(使其完成速度再快 10 倍)。

当我将此值设置为小于 0.1 时,它无法按预期工作:

// The interval to use
let interval: NSTimeInterval = 0.01 // 1.0 and 0.1 work fine, 0.01 does not

其余相关代码(此处为完整游乐场:donut builder gist):

// Extend NSTimeInterval to provide the conversion functions.
extension NSTimeInterval {

    var nSecMultiplier: Double {
        return Double(NSEC_PER_SEC)
    }

    public func nSecs() -> Int64 {
        return Int64(self * nSecMultiplier)
    }

    public func nSecs() -> UInt64 {
        return UInt64(self * nSecMultiplier)
    }

    public func dispatchTime() -> dispatch_time_t {
        // Since the last parameter takes an Int64, the version that returns an Int64 is used.
        return dispatch_time(DISPATCH_TIME_NOW, self.nSecs())
    }
}

// Define a simple function for getting a timer dispatch source.
func repeatingTimerWithInterval(interval: NSTimeInterval, leeway: NSTimeInterval, action: dispatch_block_t) -> dispatch_source_t {

    let timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue())
    guard timer != nil else { fatalError() }

    dispatch_source_set_event_handler(timer, action)

    // This function takes the UInt64 for the last two parameters
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, interval.nSecs(), leeway.nSecs())

    dispatch_resume(timer)

    return timer
}

// Create the timer
let timer = repeatingTimerWithInterval(interval, leeway: 0.0) { () -> Void in

    drawDonut()
}

// Turn off the timer after a few seconds
dispatch_after((interval * 60).dispatchTime(), dispatch_get_main_queue()) { () -> Void in
    dispatch_source_cancel(timer)

    XCPlaygroundPage.currentPage.finishExecution()
}

无法保证您为计时器设置的间隔。它只是一个目标。系统定期检查活动计时器并将其目标触发时间与当前时间进行比较,如果触发时间已过,则触发计时器。但是无法保证系统检查计时器的速度有多快。因此,目标间隔越短,线程正在做的其他工作越多,计时器的准确性就越低。来自 Apple 的文档:

A timer is not a real-time mechanism; it fires only when one of the run loop modes to which the timer has been added is running and able to check if the timer’s firing time has passed. Because of the various input sources a typical run loop manages, the effective resolution of the time interval for a timer is limited to on the order of 50-100 milliseconds. If a timer’s firing time occurs during a long callout or while the run loop is in a mode that is not monitoring the timer, the timer does not fire until the next time the run loop checks the timer. Therefore, the actual time at which the timer fires potentially can be a significant period of time after the scheduled firing time.

这似乎确实是 playground 限制。在实际 iOS 设备上进行测试时,我能够实现 0.01 秒的间隔。

尽管我最初关于 运行 循环速度限制的回答是错误的——GCD 显然能够在幕后发挥一些魔力,以允许每个 运行循环迭代。

但是,话虽这么说,您仍然应该考虑到 iOS 设备的屏幕可以刷新的最快速度是每秒 60 次,即每 0.0167 秒一次。

因此,比这更快地进行绘图更新根本没有意义。您应该考虑使用 CADisplayLink 以使绘图与屏幕刷新率同步 - 并调整您的绘图 进度 而不是计时器频率以控制进度速度。

一个相当基本的设置可能如下所示:

var displayLink:CADisplayLink?
var deltaTime:CFTimeInterval = 0
let timerDuration:CFTimeInterval = 5

func startDrawing() {

    displayLink?.invalidate()
    deltaTime = 0

    displayLink = CADisplayLink(target: self, selector: #selector(doDrawingUpdate))
    displayLink?.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes)
}

func doDrawingUpdate() {

    if deltaTime >= timerDuration {
        deltaTime = timerDuration
        displayLink?.invalidate()
        displayLink = nil
    }

    draw(CGFloat(deltaTime/timerDuration))
    deltaTime += displayLink?.duration ?? 0
}

func draw(progress:CGFloat) {
    // do drawing
}

这样您就可以确保以可用的最大帧速率进行绘制,并且如果设备承受压力并且 运行 循环因此 运行宁慢。