NSTimer 太慢
NSTimer Too Slow
类似的问题我看到过好几个了;但是,其中 none 解决了我的问题。我的计时器走得太慢了。我只使用 0.01
秒的间隔。这是我的代码:
@IBOutlet var timerLabel: UILabel!
var miliseconds = 0
var seconds = 0
func updateLabel() {
if miliseconds == 0 {
timerLabel.text = "\(seconds).00"
} else if miliseconds < 10 {
timerLabel.text = "\(seconds).0\(miliseconds)"
} else {
timerLabel.text = "\(seconds).\(miliseconds)"
}
}
var timer = NSTimer()
func updateTime() {
miliseconds++
if miliseconds == 100 {
miliseconds = 0
seconds++
}
updateLabel()
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
if timerState == 1 {
timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: "updateTime", userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
timerLabel.textColor = UIColor.blackColor()
timerState = 2
}
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
if timerState == 0 {
miliseconds = 0
seconds = 0
updateLabel()
timerLabel.textColor = UIColor.greenColor()
timerState = 1
} else if timerState == 2 {
timerState = 0
timer.invalidate()
}
}
var timerState = 0
//timerState of 0 = Has not started
//timerState of 1 = About to start
//timerState of 2 = Timing
我也试过使用延迟:
func delay(delay:Double, closure:()->()) {
dispatch_after(
dispatch_time( DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC))), dispatch_get_main_queue(), closure)
}
我在viewDidLoad
中调用了updateTime
,在updateTime
的末尾,我添加了:
delay(0.01) { () -> () in
self.updateTime()
}
不过,还是和之前一样的速度。
我该如何解决这个问题?如果我在研究过程中遗漏了一个问题,请告诉我。谢谢!
这里有一大堆问题。让我们清理一下。
class ViewController: UIViewController {
@IBOutlet var timerLabel: UILabel!
首先,不要使用计时器来更新标签。使用 CADisplayLink
。显示 link 与屏幕刷新间隔同步,在大多数 iOS 设备上为 1/60 秒(不是 1/100),因此您不需要做额外的工作:
private var displayLink: CADisplayLink?
接下来,不要尝试在计时器(或 link)触发时通过增加计数器来跟踪经过的时间,因为计时器或 link 不能保证触发的频率你要求。相反,存储计时器启动的时间和停止时间(如果停止):
private var startTime: CFAbsoluteTime = 0
private var endTime: CFAbsoluteTime = 0 {
didSet {
updateLabel()
}
}
使用 enum
而不是神秘的硬编码数字来跟踪状态。由于标签颜色仅取决于状态,因此向状态添加 属性 以提供该状态的标签颜色:
private enum State {
case Stopped
case Pending
case Running
var labelColor: UIColor {
switch self {
case .Pending: return UIColor.greenColor()
case .Stopped, .Running: return UIColor.blackColor()
}
}
}
经过的时间取决于状态,所以添加一个方法来计算它:
private var elapsedTime: NSTimeInterval {
switch state {
case .Stopped: return endTime - startTime
case .Running: return CFAbsoluteTimeGetCurrent() - startTime
case .Pending: return 0
}
}
更新标签时使用格式字符串将经过的时间转换为字符串:
private func updateLabel() {
timerLabel.text = String(format: "%.02f", elapsedTime)
}
更改计时器状态可以同时更改标签颜色和已用时间,因此在状态更改时更新标签颜色和标签文本:
private var state = State.Stopped {
didSet {
timerLabel.textColor = state.labelColor
updateLabel()
}
}
触摸开始时,根据需要创建显示 link,然后更新状态。该州的 didSet
将根据需要处理更新标签:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
createDisplayLinkIfNeeded()
switch state {
case .Stopped:
state = .Pending
case .Pending:
break
case .Running:
state = .Stopped
endTime = CFAbsoluteTimeGetCurrent()
displayLink?.paused = true
}
}
触摸结束后,必要时启动计时器:
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
if state == .Pending {
startTime = CFAbsoluteTimeGetCurrent()
displayLink?.paused = false
state = .Running
}
}
创建显示的方法如下 link:
private func createDisplayLinkIfNeeded() {
guard self.displayLink == nil else { return }
let displayLink = CADisplayLink(target: self, selector: "displayLinkDidFire:")
displayLink.paused = true
displayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes)
self.displayLink = displayLink
}
下面是显示 link 将调用的方法:
func displayLinkDidFire(_: CADisplayLink) {
updateLabel()
}
} // end of ViewController
从我上面的评论...(鼓励)
NSTimer - 来自苹果开发者文档:
"A timer is not a real-time mechanism; it fires only when one of the
run loop modes to which the timer has been added is running and able
to check if the timer’s firing time has passed. Because of the various
input sources a typical run loop manages, the effective resolution of
the time interval for a timer is limited to on the order of 50-100
milliseconds. "
类似的问题我看到过好几个了;但是,其中 none 解决了我的问题。我的计时器走得太慢了。我只使用 0.01
秒的间隔。这是我的代码:
@IBOutlet var timerLabel: UILabel!
var miliseconds = 0
var seconds = 0
func updateLabel() {
if miliseconds == 0 {
timerLabel.text = "\(seconds).00"
} else if miliseconds < 10 {
timerLabel.text = "\(seconds).0\(miliseconds)"
} else {
timerLabel.text = "\(seconds).\(miliseconds)"
}
}
var timer = NSTimer()
func updateTime() {
miliseconds++
if miliseconds == 100 {
miliseconds = 0
seconds++
}
updateLabel()
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
if timerState == 1 {
timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: "updateTime", userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
timerLabel.textColor = UIColor.blackColor()
timerState = 2
}
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
if timerState == 0 {
miliseconds = 0
seconds = 0
updateLabel()
timerLabel.textColor = UIColor.greenColor()
timerState = 1
} else if timerState == 2 {
timerState = 0
timer.invalidate()
}
}
var timerState = 0
//timerState of 0 = Has not started
//timerState of 1 = About to start
//timerState of 2 = Timing
我也试过使用延迟:
func delay(delay:Double, closure:()->()) {
dispatch_after(
dispatch_time( DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC))), dispatch_get_main_queue(), closure)
}
我在viewDidLoad
中调用了updateTime
,在updateTime
的末尾,我添加了:
delay(0.01) { () -> () in
self.updateTime()
}
不过,还是和之前一样的速度。
我该如何解决这个问题?如果我在研究过程中遗漏了一个问题,请告诉我。谢谢!
这里有一大堆问题。让我们清理一下。
class ViewController: UIViewController {
@IBOutlet var timerLabel: UILabel!
首先,不要使用计时器来更新标签。使用 CADisplayLink
。显示 link 与屏幕刷新间隔同步,在大多数 iOS 设备上为 1/60 秒(不是 1/100),因此您不需要做额外的工作:
private var displayLink: CADisplayLink?
接下来,不要尝试在计时器(或 link)触发时通过增加计数器来跟踪经过的时间,因为计时器或 link 不能保证触发的频率你要求。相反,存储计时器启动的时间和停止时间(如果停止):
private var startTime: CFAbsoluteTime = 0
private var endTime: CFAbsoluteTime = 0 {
didSet {
updateLabel()
}
}
使用 enum
而不是神秘的硬编码数字来跟踪状态。由于标签颜色仅取决于状态,因此向状态添加 属性 以提供该状态的标签颜色:
private enum State {
case Stopped
case Pending
case Running
var labelColor: UIColor {
switch self {
case .Pending: return UIColor.greenColor()
case .Stopped, .Running: return UIColor.blackColor()
}
}
}
经过的时间取决于状态,所以添加一个方法来计算它:
private var elapsedTime: NSTimeInterval {
switch state {
case .Stopped: return endTime - startTime
case .Running: return CFAbsoluteTimeGetCurrent() - startTime
case .Pending: return 0
}
}
更新标签时使用格式字符串将经过的时间转换为字符串:
private func updateLabel() {
timerLabel.text = String(format: "%.02f", elapsedTime)
}
更改计时器状态可以同时更改标签颜色和已用时间,因此在状态更改时更新标签颜色和标签文本:
private var state = State.Stopped {
didSet {
timerLabel.textColor = state.labelColor
updateLabel()
}
}
触摸开始时,根据需要创建显示 link,然后更新状态。该州的 didSet
将根据需要处理更新标签:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
createDisplayLinkIfNeeded()
switch state {
case .Stopped:
state = .Pending
case .Pending:
break
case .Running:
state = .Stopped
endTime = CFAbsoluteTimeGetCurrent()
displayLink?.paused = true
}
}
触摸结束后,必要时启动计时器:
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
if state == .Pending {
startTime = CFAbsoluteTimeGetCurrent()
displayLink?.paused = false
state = .Running
}
}
创建显示的方法如下 link:
private func createDisplayLinkIfNeeded() {
guard self.displayLink == nil else { return }
let displayLink = CADisplayLink(target: self, selector: "displayLinkDidFire:")
displayLink.paused = true
displayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes)
self.displayLink = displayLink
}
下面是显示 link 将调用的方法:
func displayLinkDidFire(_: CADisplayLink) {
updateLabel()
}
} // end of ViewController
从我上面的评论...(鼓励)
NSTimer - 来自苹果开发者文档:
"A timer is not a real-time mechanism; it fires only when one of the run loop modes to which the timer has been added is running and able to check if the timer’s firing time has passed. Because of the various input sources a typical run loop manages, the effective resolution of the time interval for a timer is limited to on the order of 50-100 milliseconds. "