为什么减少间隔并不能加快iOS计时器的执行



当我运行此计时器代码60秒持续时间/1秒间隔或6秒/.1秒间隔时,它会按预期工作(完成速度快10倍)。然而,将值降低到0.6秒/0.01秒并不能像预期的那样加快整个操作的速度(使其再快10倍)。

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

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

相关代码的其余部分(此处为完整的操场:甜甜圈构建者要点):

// 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()
}

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

计时器不是实时机制;只有当添加了计时器的运行循环模式正在运行并且能够以检查计时器的启动时间是否已过。由于各种典型运行循环管理的输入源计时器的时间间隔被限制在50-100的数量级毫秒。如果计时器的启动时间发生在长调用期间,或当运行循环处于不监视计时器的模式时直到下次运行循环检查计时器时,计时器才会启动。因此,计时器可能触发的实际时间可以是在预定的发射时间之后的相当长的一段时间。

这确实是一个操场限制。在实际的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
}

这样,您可以确保以可用的最大帧速率绘制,并且如果设备处于紧张状态,因此运行循环运行速度较慢,则绘制进度不会受到影响。

最新更新