NSTimer太慢

时间:2016-02-19 02:21:00

标签: ios swift time nstimer

我见过类似的多个问题;但是,他们都没有解决我的问题。我的计时器太慢了。我只使用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)

}

我在updateTime中致电viewDidLoad,在updateTime结束时,我补充道:

delay(0.01) { () -> () in
    self.updateTime()
}

然而,它仍然以与以前相同的速度前进。

如何解决此问题?如果我在研究过程中遗漏了一个问题,请告诉我。谢谢!

2 个答案:

答案 0 :(得分:7)

这里有一大堆问题。让我们清理一下。

class ViewController: UIViewController {

    @IBOutlet var timerLabel: UILabel!

首先,不要使用计时器来更新标签。使用CADisplayLink。显示链接与屏幕刷新间隔同步,在大多数iOS设备(不是1/100)上是1/60秒,因此您不需要做额外的工作:

    private var displayLink: CADisplayLink?

接下来,不要在计时器(或链接)触发时通过递增计数器来跟踪已用时间,因为计时器或链接不能保证按照您的请求频繁触发。而是存储计时器启动的时间,以及计时器停止的时间(如果它已停止):

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

触摸开始时,根据需要创建显示链接,然后更新状态。州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
        }
    }

以下是创建显示链接的方法:

    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
    }

以下是显示链接将调用的方法:

    func displayLinkDidFire(_: CADisplayLink) {
        updateLabel()
    }

} // end of ViewController

答案 1 :(得分:0)

从我上面的评论中......(鼓励)

NSTimer - 来自apple developer docs:

  

“计时器不是实时机制;只有当其中一个计时器触发时它才会触发   已添加计时器的运行循环模式正在运行且能够运行   检查计时器的开火时间是否已经过去。因为各种各样   输入源一个典型的运行循环管理,有效的分辨率   计时器的时间间隔限制在50-100的量级   毫秒。 “