尝试使用 GCD 创建一个准确的计时器 [英] Trying to create an accurate timer with GCD

查看:27
本文介绍了尝试使用 GCD 创建一个准确的计时器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试制作一个需要非常精确的计时器(需要与背景音乐同步)的音乐应用.而且我还需要在 UI 上将计时器显示为进度条.

I'm trying to make a music app that needs a very precise timer (it needs to be synced with background music). And I need to display the timer as a progress bar on the UI too.

我最初使用 NSTimer,结果完全不准确,关闭了 20 毫秒以上.我转向 GCD.但我似乎也无法让它工作.

I initially started with NSTimer, turned out to be not accurate at all, with above 20ms off. And I turned to GCD. But I seem not to be able to get it work neither.

这是我的代码(删除了不相关的部分)

Here is my code (removed parts that are not related)

这是我的 ViewController 类的第一行

This is the first line in my ViewController class

var dispatchQueue = dispatch_queue_t()

在我的override func viewDidLoad() 函数中

dispatchQueue = dispatch_queue_create("myQueueId", nil)

然后是使用调度队列的实际功能

Then the actual functions that use the dispatch queue

// Generate new measure
// In the main dispatch queue because it handles a lot of work
func newMeasure() {
    timerBar.progress = 1.0
    // Reset the timer
    logEnd = NSDate()
    let lapse = logEnd.timeIntervalSinceDate(logStart)
    println("Lapse: \(lapse)")
    logStart = NSDate()

    // measureTimer is set to 1.47, so the newMeasure is supposedly to be called every 1.47 seconds
    timer = measureTimer

    startTime = NSDate()
    dispatch_async(dispatchQueue, {
        self.gameTimer()
    })

    // ... do a lot of stuff that will drag the timer if not put in dispatch queue
}

// Accurate Game Timer
// In the dispacheQueue so the timer can has its own thread
func gameTimer() {
    // Get the time now
    let now = NSDate()
    let adjust = now.timeIntervalSinceDate(self.startTime)
    // Refresh the start time to be now
    self.startTime = now
    self.timer = self.timer - adjust

    // Go back to the main dispatch queue to update UI
    dispatch_async(dispatch_get_main_queue(), {
        self.timerBar.progress = Float(self.timer / self.measureTimer)
    })

    if (self.timer <= 0.2) {
        dispatch_async(dispatch_get_main_queue(), {
            // Going back to the main dispatch queue to start another new measure
            NSTimer.scheduledTimerWithTimeInterval(self.timer, target: self, selector: Selector("newMeasure"), userInfo: nil, repeats: false)
        })
    }
    else {
        dispatch_after(dispatch_time_t(
            100 * NSEC_PER_MSEC), dispatchQueue, {
                self.gameTimer()
        })
    }
}

但实际的日志是这样的:

But the actual log looks like this:

Lapse: 1.47152501344681
Lapse: 1.51420003175735
Lapse: 1.47065001726151
Lapse: 1.47149801254272
Lapse: 1.471755027771
Lapse: 1.47201299667358
Lapse: 1.47167503833771
Lapse: 1.47148901224136
Lapse: 1.47146201133728
Lapse: 1.47289103269577
Lapse: 1.47580003738403
Lapse: 1.47087097167969
Lapse: 1.47286003828049
Lapse: 1.47235900163651
Lapse: 1.47159999608994
Lapse: 1.47144496440887
Lapse: 1.50616401433945
Lapse: 1.51679295301437
Lapse: 1.47380495071411
Lapse: 1.47130501270294
Lapse: 1.50468301773071
Lapse: 1.4718160033226
Lapse: 1.49480104446411
Lapse: 1.50195497274399
Lapse: 1.50018000602722
Lapse: 1.47339296340942
Lapse: 1.47266495227814

他们仍然不那么准时.而且因为时间对音乐非常敏感,所以这是行不通的.有人可以帮我解决这个准确性问题吗?谢谢

They still are not so on time. And because time is so sensitive to music, this is not gonna work. Can someone help me with this accuracy issue? Thank you

推荐答案

您应该考虑完全停用 NSTimer 并创建一个调度计时器,它有一个 leeway 参数,您可以在其中指定多少纬度正在让计时器运行.此外,如果您尝试在后台线程上执行计时器,这是一种更简单、更有效的方法:

You should consider retiring NSTimer altogether and create a dispatch timer, which has a leeway parameter in which you can specify how much latitude you're giving the timer to run. Also, if you're trying to do timers on a background thread, it's a far simpler and more efficient way of doing that:

private var timer: dispatch_source_t!
private var start: CFAbsoluteTime!

func startTimer() {
    let queue = dispatch_queue_create("com.domain.app.timer", nil)
    timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue)
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, UInt64(1.47 * Double(NSEC_PER_SEC)), 0) // every 1.47 seconds, with no leeway
    dispatch_source_set_event_handler(timer) {
        let current = CFAbsoluteTimeGetCurrent()
        NSLog(String(format: "%.4f", current - self.start))
        self.start = current
    }

    start = CFAbsoluteTimeGetCurrent()
    dispatch_resume(timer)
}

func stopTimer() {
    dispatch_source_cancel(timer)
    timer = nil
}

这不会恰好每 1.47 秒发生一次,但可能会更接近.

This isn't going to happen precisely every 1.47 seconds, but it might be closer.

注意,我避免来回调度东西和/或安排新的计时器或 dispatch_after,不过.我创建了一个调度计时器,然后让它离开.另外,我避免使用 NSDate 并使用 CFAbsoluteTime,这应该更有效.

Note, I'm avoid dispatching stuff back and forth and/or scheduling new timers or dispatch_after, though. I create a single dispatch timer and let it go. Also, I'm avoiding NSDate and using CFAbsoluteTime, which should be more efficient.

这篇关于尝试使用 GCD 创建一个准确的计时器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆