试图用GCD创build一个精确的计时器

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

我最初开始使用NSTimer ,结果是不准确的,超过20ms。 我转向了GCD。 但是,我似乎也无法得到它的工作。

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

这是我的ViewController类的第一行

 var dispatchQueue = dispatch_queue_t() 

并在我的override func viewDidLoad()函数

 dispatchQueue = dispatch_queue_create("myQueueId", nil) 

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

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

但是实际的日志看起来像这样:

 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 

他们还没有那么及时。 而且,因为时间对音乐如此敏感,这是不会奏效的。 有人可以帮助我解决这个准确性问题? 谢谢

你实际上并没有使用GCD定时器。 一个GCD定时器将涉及一个调度源,并且在你的代码中没有调度源。

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

您应该考虑完全NSTimer并创build一个调度计时器,该计时器有一个余地参数,您可以在其中指定给定计时器运行的范围。 另外,如果你想在后台线程上执行定时器,这是一个更简单,更高效的方法:

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

请注意,尽pipe我避免了来callback度和/或安排新的计时器或dispatch_after 。 我创build一个调度计时器,让它离开。 此外,我避免NSDate和使用CFAbsoluteTime ,应该更有效。

请记住:即使您将dispatch_async分配给主线程,仍然必须分派。 所以捕获self.timer的状态和更新UI之间有一个有限的时间段。 来源: https : //developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html

NSTimer不应该用于精确的测量。 资源:

https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSTimer_Class/index.html

下面是我正在使用的示例类,它使用mach_absolute_time。 它只是一个存根,我仍然需要充实,但它可能会让你继续下去。

标题:

 #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 

执行:

 #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