Ios 尝试使用GCD创建准确的计时器

Ios 尝试使用GCD创建准确的计时器,ios,swift,timer,Ios,Swift,Timer,我正在尝试制作一个音乐应用程序,它需要一个非常精确的计时器(它需要与背景音乐同步)。我还需要在UI上将计时器显示为进度条 我最初使用的是NSTimer,结果是根本不准确,关闭了20毫秒以上。我转向了GCD。但我似乎也无法让它工作 这是我的代码(删除了不相关的部分) 这是我的ViewController类的第一行 var dispatchQueue = dispatch_queue_t() 在myoverride func viewDidLoad()函数中 dispatchQueue = dis

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

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

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

这是我的ViewController类的第一行

var dispatchQueue = dispatch_queue_t()
在my
override func viewDidLoad()函数中

dispatchQueue = dispatch_queue_create("myQueueId", nil)
然后是使用分派队列的实际函数

// 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()
        })
    }
}
他们仍然不是那么准时。因为时间对音乐如此敏感,这是行不通的。 有人能帮我解决这个准确性问题吗?
谢谢

您实际上没有使用GCD计时器。GCD计时器将涉及分派源,并且代码中没有分派源


然而,即使如此,获得计时器的唯一真正准确的方法是通过CADisplayLink。它是一个内置计时器,大约每1/60秒触发一次,因为它与硬件本身的刷新率有关。即使CADisplayLink也不是非常精确,但它很接近,并且尽可能频繁,并且每次触发都包含一个时间戳,因此您可以非常精确地进行补偿。

请记住:即使您将异步调度到主线程,它仍然必须被调度。因此,在捕获self.timer的状态和更新UI之间有一个有限的时间段。资料来源:

N计时器不应用于精确测量。资料来源:

这是我正在处理的一个示例类,它使用马赫绝对时间。它只是一个存根,我仍然需要充实它,但它可能会给你一些东西继续下去

标题:

#import <Foundation/Foundation.h>
#include <mach/mach_time.h>


@interface TimeKeeper : NSObject
{
    uint64_t timeZero;

}

+ (id) timer;

- (void) start;
- (uint64_t) elapsed;
- (float) elapsedSeconds;

@end

你应该考虑退役<代码> NSTIMER ,并创建一个调度计时器,它有一个余量参数,在这里你可以指定给定时器运行的纬度有多大。此外,如果您试图在后台线程上执行计时器,这是一种更简单、更有效的方法:

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秒发生一次,但可能会更近


注意,我避免来回发送东西和/或安排新的计时器或
之后发送。我创建了一个调度计时器,然后让它去。另外,我正在避免使用
NSDate
并使用
CFAbsoluteTime
,这应该会更有效。

请添加您初始化dispatchQueue的方式。您是否尝试过GCD调度源计时器?@JinWang您实际上没有使用GCD计时器。您仍在使用NSTimer。事实上,你用GCD来称呼它是无关紧要的。如果您想查看GCD计时器,请看以下内容:如果您试图同步音频曲目,您可能应该使用核心音频,而不使用计时器。请看。这可能是让计时器工作得相当可靠的最快最简单的方法。基本上是3行代码。这比使用@matt的顶级延迟函数更准确?@matt我如何使用CADisplayLink实现@PlateReverb,我想你可以试试mach内核API,比如
mach\u wait\u,直到
#import "TimeKeeper.h"

static mach_timebase_info_data_t timeBase;

@implementation TimeKeeper

+ (void) initialize
{
    (void) mach_timebase_info( &timeBase );
}

+ (id) timer
{
    return [[[self class] alloc] init];
}

- (id) init
{
    if( (self = [super init]) )
    {
        timeZero = mach_absolute_time();
    }
    return self;
}

- (void) start
{
    timeZero = mach_absolute_time();
}

- (uint64_t) elapsed
{
    return mach_absolute_time() - timeZero;
}

- (float) elapsedSeconds
{
    return ((float)(mach_absolute_time() - timeZero))
    * ((float)timeBase.numer) / ((float)timeBase.denom) / 1000000000.0f;
}

//TODO: Add a Pause method.

@end
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
}