我正在尝试制作一款需要非常精确定时器的音乐应用(需要与背景音乐同步)。我还需要在UI上将计时器显示为进度条。
我最初以NSTimer
开始,结果是不准确,超过20毫秒。我转向GCD。但我似乎也无法让它工作。
这是我的代码(删除了无关的部分)
这是我的ViewController类的第一行
var dispatchQueue = dispatch_queue_t()
在我的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()
})
}
}
但实际日志看起来像这样:
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
他们仍然不是那么准时。而且因为时间对音乐如此敏感,这是行不通的。 有人可以帮我解决这个准确性问题吗? 谢谢
答案 0 :(得分:3)
您实际上并未使用GCD计时器。 GCD计时器将涉及一个调度源,您的代码中没有调度源。
然而,即便如此,获得计时器的唯一真正准确的方法是通过CADisplayLink。它是一个内置的计时器,大约每1/60秒触发一次,因为它与硬件本身的刷新率有关。甚至CADisplayLink也不是很准确,但它很接近,并且它尽可能频繁,并且每次射击都包含一个时间戳,因此你可以非常精确地补偿。
答案 1 :(得分:3)
您应该考虑完全退出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秒发生一次,但可能更接近。
注意,我会避免来回调度和/或安排新的计时器或dispatch_after
。我创建了一个调度计时器,让它去。另外,我正在避免NSDate
并使用CFAbsoluteTime
,这应该更有效率。
答案 2 :(得分:0)
请记住:即使您将dispatch_async分配给主线程,仍然需要调度它。因此,在捕获self.timer的状态和更新UI之间存在有限的时间段。来源:https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html
NSTimer不应用于精确测量。来源:
以下是我正在使用的示例类,它使用的是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