I need a reliable StopWatch in Swift 3 - swift

I need a reliable StopWatch in Swift 3. I came up with this based on another example found on SO ...
import Foundation
class StopWatch {
public private(set) var seconds:Double = 0
public var milliseconds: Double { return seconds * 1000 }
private weak var timer:Timer?
private var startTime:Double = 0
public private(set) var started = false
public func start() {
if started { return }
started = true
startTime = Date().timeIntervalSinceReferenceDate
timer = Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) {
timer in
if !self.started { return }
self.seconds = Date().timeIntervalSinceReferenceDate - self.startTime
print("Seconds: \(self.seconds)")
}
print("Started")
}
public func stop() {
if !started { return }
timer?.invalidate()
started = false
print("Stopped")
}
}
But this doesn't seem to work reliably. Fetching seconds are often staying at 0 or in other cases when accessing seconds they don't start from 0. What is wrong with this code?

It's most likely a threading issue. If UI is involved you have to make sure that the UI related code runs on the main thread.
The variable started is redundant. Checking the timer for nil does the same.
This code adds a DispatchQueue.main block. Put your code to update the UI into this block
class StopWatch {
public private(set) var seconds:Double = 0
public var milliseconds: Double { return seconds * 1000 }
private weak var timer : Timer?
private var startTime : Double = 0
private var timerIsRunning : Bool { return timer != nil }
public func start() {
if timerIsRunning { return }
startTime = Date().timeIntervalSinceReferenceDate
timer = Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { timer in
self.seconds = Date().timeIntervalSinceReferenceDate - self.startTime
print("Seconds: \(self.seconds)")
DispatchQueue.main.async {
// do something on the main thread
}
}
print("Started")
}
public func stop() {
if !timerIsRunning { return }
timer!.invalidate()
timer = nil
print("Stopped")
}
}

Related

Timer fires twice then doesn't reset

I've got a countdown timer that counts down and executes the code twice but then it doesn't reset, instead it continues counting in negative numbers. Can someone tell me why?
var didCount = 4
func startDelayTimer() {
delayTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { _ in
self.startDelayCount()
})
}
func startDelayCount() {
delayTime -= 1
timeLbl.text = String(delayTime)
if delayTime <= 3 {
soundPLayer.play()
}
if delayTime == 0 {
delayTimer.invalidate()
doSomething()
}
}
func doSomething() {
doCount += 1
if doCount < didCount {
startDelayTimer()
}
else {
print("done")
}
}
The direct issue is that you reset the timer without remebering to reset delayTime.
But I think there's also an architectural issue, in that you have a murky mix of responsibilities (managing a timer, updating a label, and playing sounds). I'd suggest you extract the timer responsibilities elsewhere.
Perhaps something along these lines:
/// A timer which counts from `initialCount` down to 0, firing the didFire callback on every count
/// After each full countdown, it repeats itself until the repeatLimit is reached.
class RepeatingCountDownTimer {
typealias FiredCallback: () -> Void
typealias FinishedCallback: () -> Void
private var initialCount: Int
private var currentCount: Int // Renamed from old "delayTime"
private var repeatCount = 0 // Renamed from old "doCount"
private let repeatLimit: Int // Renamed from old "didCount"
private var timer: Timer?
private let didFire: FiredCallback
private let didFinish: FinishedCallback
init(
countDownFrom initialCount: Int,
repeatLimit: Int,
didFire: #escaping FiredCallback,
didFinish: #escaping FinishedCallback
) {
self.initialCount = initialCount
self.currentCount = initialCount
self.repeatLimit = repeatLimit
self.didFire = didFire
self.didFinish = didFinish
}
public func start() {
self.currentCount = self.initialCount
self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { [weak self] in
self?.fire()
})
}
private func fire() {
currentCount -= 1
self.didFire(currentCount)
if currentCount == 0 {
repeat()
}
}
private func repeat() {
repeatCount += 1
if repeatCount < repeatLimit {
self.timer?.invalidate()
start()
} else {
finished()
}
}
private func finished() {
self.timer?.invalidate()
self.timer = nil
self.didFinish()
}
}
That's just rough psuedo-code, which will certainly need tweaking. But the idea is to separate timer and state management from the other things you need to do. This should make it easier to debug/develop/test this code, replacing useless names like doSomething with more concretely named events.
The usage might look something like:
let countDownTimer = RepeatingCountDownTimer(
countDownFrom: 4,
repeatLimit: 4,
didFire: { count in
timeLbl.text = String(count)
soundPlayer.play()
},
didFinish: { print("done") }
)
countDownTimer.start()

How to Test Timer with #Published?

I created a view which uses a ObservableObject which used an Timer to update seconds which are an #Published property.
class TimerService: ObservableObject {
#Published var seconds: Int
var timer: Timer?
convenience init() {
self.init(0)
}
init(_ seconds: Int){
self.seconds = seconds
}
func start() {
...
self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
self.seconds += 1 }
self.timer?.fire()
}
func stop() {...}
func reset() {...}
}
To test this logic I tried to subscribe to the seconds var. The problem is that the .sink method only trigger once and never again, even when it should.
class WorkTrackerTests: XCTestCase {
var timerService: TimerService!
override func setUpWithError() throws {
super.setUp()
timerService = TimerService()
}
override func tearDownWithError() throws {
super.tearDown()
timerService = nil
}
func test_start_timer() throws {
var countingArray: [Int] = []
timerService.start()
timerService.$seconds.sink(receiveValue: { value -> Void in
print(value) // 1 (called once with this value)
countingArray.append(value)
})
timerService.stop()
for index in 0...countingArray.count-1 {
if(index>0) {
XCTAssertTrue(countingArray[index] - 1 == countingArray[index-1])
}
}
}
}
Is there something I did wrong or is the SwiftUI #Published Wrapper not capable of being subscribed by something else than SwiftUI itself?
I'll start by repeating what I said already in comments. There is no need to test Apple's code. Don't test Timer. You know what it does. Test your code.
As for your actual example test harness, it is flawed from top to bottom. A sink without a store will indeed get only one value, if it gets any at all. But the issue runs even deeper, as you are acting like your code will magically stop and wait for the timer to finish. It won't. You are saying stop immediately after saying start, so the timer never even runs. Asynchronous input requires asynchronous testing. You would need an expectation and a waiter.
But it is very unclear why you are subscribing to the publisher at all. What are you trying to find out? The only question of interest, it seems, is whether you are incrementing your variable each time the timer fires. And you can test that without subscribing to a publisher — and, as I said, without a Timer.
So much for the repetition. Now let's demonstrate. Let's start with the code you've actually shown, the only code that has content:
class TimerService: ObservableObject {
#Published var seconds: Int
var timer: Timer?
convenience init() {
self.init(0)
}
init(_ seconds: Int){
self.seconds = seconds
}
func start() {
self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
self.seconds += 1
}
self.timer?.fire()
}
}
Now look at all the commands you send to the Timer and encapsulate them in a Timer subclass:
class TimerMock : Timer {
var block : ((Timer) -> Void)?
convenience init(block: (#escaping (Timer) -> Void)) {
self.init()
self.block = block
}
override class func scheduledTimer(withTimeInterval interval: TimeInterval,
repeats: Bool,
block: #escaping (Timer) -> Void) -> Timer {
return TimerMock(block:block)
}
override func fire() {
self.block?(self)
}
}
Now make your TimerService a generic so that we can inject TimerMock when testing:
class TimerService<T:Timer>: ObservableObject {
#Published var seconds: Int
var timer: Timer?
convenience init() {
self.init(0)
}
init(_ seconds: Int){
self.seconds = seconds
}
func start() {
self.timer = T.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
self.seconds += 1
}
self.timer?.fire()
}
}
So now we can test your logic without bothering to run a timer:
import XCTest
#testable import TestingTimer // whatever the name of your module is
class TestingTimerTests: XCTestCase {
func testExample() throws {
let timerService = TimerService<TimerMock>()
timerService.start()
if let timer = timerService.timer {
timer.fire()
timer.fire()
timer.fire()
}
XCTAssertEqual(timerService.seconds,4)
}
}
None of your other code needs to change; you can go on using TimerService as before. I can think of other ways to do this, but they would all involve dependency injection where in "real life" you use a Timer but when testing you use a TimerMock.

Timer.scheduledTimer not firing

I am trying to calculate the time it takes a vehicle to go between two checkpoints. I have setup the following timer to achieve this.
func startTimer() {
if hasStarted == true && timerStarted == false {
print("Timing started!")
gameTimer = Timer.scheduledTimer(timeInterval: 0.001, target: self, selector: (#selector(activeTiming)), userInfo: nil, repeats: true)
timerStarted = true
}
}
#objc func activeTiming() {
print("Active timing block")
if(hasFinished == false) {
gameTime = gameTime + 0.001
print("Add time succeeded")
} else {
gameTimer?.invalidate()
}
}
The expected output would be the following:
Timing started!
Active timing block
Add time succeeded
Add time succeeded ... etc
The actual output:
Timing started!
So it would appear that the startTimer is properly being called but the timer is not firing the activeTiming block of code. Any suggestions would be extremely helpful. Thank you for your help in advance.
Posting this code as it is what I'm using, but I’m no expert on Swift, so your mileage may vary!
class PerformanceTest {
var name: String = ""
var tolerance: Int64 = 0
var lastTime: Int64 = 0
var thisTime: Int64 = 0
var delta: Int64 = 0
var percent: Float = 0
func setTolerance(vName: String, vTolerance: Int64) {
name = vName
tolerance = vTolerance
}
func reset() {
delta = 0
percent = Float((Float(delta) / Float(tolerance))) * 100
//high = 0
}
func start() {
lastTime = Date().toMillis()
}
func finish() {
thisTime = Date().toMillis()
let vDelta = thisTime - lastTime
if(vDelta > delta) {
delta = vDelta
percent = Float((Float(delta) / Float(tolerance))) * 100
if(delta > tolerance) {
print("Performance Indicator: \(name) Above Tolerance" + String(format: "%3.0f", percent) + "%")
}
}
}
func display() -> String {
//high = delta
//print("\(vString) Tolerance: \(tolerance) Max: \(high)")
return String(format: "%3.0f", percent) + "% |"
}
}
extension Date {
func toMillis() -> Int64! {
return Int64(self.timeIntervalSince1970 * 1000)
}
Usage:
var performanceDefenseLoop = PerformanceTest()
performanceDefenseLoop.setTolerance(vName: "DefenseLoop", vTolerance: 150)
func timeToUpdateDefenses()
{
performanceDefenseLoop.start()
defesensesLoop()
performanceDefenseLoop.finish()
print("\(performanceDefenseLoop.Display())"
}
// To reset
performanceDefenseLoop.reset()
Be sure to start timers in the main thread!
I also faced a similar issue where the timer body was not getting called. I found out that the timer was getting scheduled from a background thread instead of the main thread. Wrapping it in DispatchQueue.main.async fixed it.
DispatchQueue.main.async {
self.pingTimer = Timer.scheduledTimer(withTimeInterval: timerInterval, repeats: true, block: { [weak self] timer in
//
Logger.shared.log(.anyCable, .debug, "timer run")
self?.sendPing(sender: self)
})
}
Your code works for me, but that isn't how you want to time an event because the Timer isn't that accurate, and you are wasting a lot of computing time (read battery).
Instead, I suggest the following approach:
When the event starts, record the start time:
let startTime = Date()
When the event ends, compute the elapsed time:
let elapsedTime = Date().timeIntervalSince(startTime)
elapsedTime will be in seconds (including fractional seconds).

Swift Timer only firing once in a operation [duplicate]

This question already has answers here:
NSTimer callback on background thread
(2 answers)
Why would a `scheduledTimer` fire properly when setup outside a block, but not within a block?
(3 answers)
Trying to Understand Asynchronous Operation Subclass
(3 answers)
Closed 4 years ago.
I have this code set up in a subclass of an Operation in Swift. But I can't figure out why the timer is only calling the selector once.
class DogOperationRun : DogOperation {
let thisDog:Dog?
let operationID:Int?
var runDistance:Double?
var dogSpeed:Double?
var runTimeLeft:Double = 0.0
var currentRunDistance:Double = 0.0
private var interval = 0.0
private var cycle = 0.0
private var dogTimer:Timer?
init(thisDog:Dog, operationID:Int, runDistance:Double, dogSpeed:Double) {
self.thisDog = thisDog
self.operationID = operationID
self.runDistance = runDistance
self.dogSpeed = dogSpeed
super.init(self.operationID!)
}
override func main() {
guard isCancelled == false else {
operationIsFinished = true
return
}
startRun()
}
func startRun() {
operationIsExecuting = true
if let distance = runDistance, let thisDogSpeed = dogSpeed {
runTimeLeft = distance
interval = distance/thisDogSpeed
dogTimer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(runDog), userInfo: nil, repeats: true)
}
}
#objc func runDog() {
runTimeLeft -= interval
cycle += 1
if runTimeLeft > 0 {
runDistance = interval * cycle
print("\(self.thisDog!.name) has run \(runDistance!) meters!")
} else {
dogTimer?.invalidate()
operationIsFinished = true
}
}
}
and the superclass
import Foundation
class DogOperation: Operation {
public var id:Int
//going to over ride the existing variable and return my private one
override var isExecuting: Bool {
return operationIsExecuting
}
var operationIsExecuting = false {
didSet {
willChangeValue(forKey: "isExecuting")
print("Operation \(id) will execute")
didChangeValue(forKey: "isExecuting")
print("Operation \(id) is executing")
}
}
override var isFinished: Bool {
return operationIsFinished
}
var operationIsFinished = false {
didSet {
willChangeValue(forKey: "isFinished")
print("Operation \(id) will finish")
didChangeValue(forKey: "isFinished")
print("Operation \(id) is finished")
}
}
init(_ id:Int) {
self.id = id
}
}
calling it like so:
let lodiDogRun = DogOperationRun(thisDog: aDog, operationID: 1, runDistance: 100.0, dogSpeed: 12.0)
let pretzelDogRun = DogOperationRun(thisDog: nextDog, operationID: 2, runDistance: 100.0, dogSpeed: 14.0)
myOperationQueue.addOperations([lodiDogRun, pretzelDogRun], waitUntilFinished: true)
Edit for Matt:The only time dogRun gets called is when I force the timer with dogTimer.fire(). For the instance I captured it on it was running on thread 5:

Swift : Pause and Resume NSTimer

I know this has been posted before, but I'm new to swift and i would like to find help as per my situation.
So i need to run a timer, and when the stopMusic() is called(somewhere in my app), i need to pause the timer, then when the playMusic() is called, i need to resume the timer.
Here is what i have:
override func viewDidLoad() {
runFirstDropTimer = NSTimer.scheduledTimerWithTimeInterval(38.2, target: self, selector: "runAnimation", userInfo: nil, repeats: false)
}
func stopMusic() {
//Pause Timer
}
func playMusic() {
//Resume Timer
}
Thank you for any help you can provide!!!
Swift 5.
Resumable timer:
class ResumableTimer: NSObject {
private var timer: Timer? = Timer()
private var callback: () -> Void
private var startTime: TimeInterval?
private var elapsedTime: TimeInterval?
// MARK: Init
init(interval: Double, callback: #escaping () -> Void) {
self.callback = callback
self.interval = interval
}
// MARK: API
var isRepeatable: Bool = false
var interval: Double = 0.0
func isPaused() -> Bool {
guard let timer = timer else { return false }
return !timer.isValid
}
func start() {
runTimer(interval: interval)
}
func pause() {
elapsedTime = Date.timeIntervalSinceReferenceDate - (startTime ?? 0.0)
timer?.invalidate()
}
func resume() {
interval -= elapsedTime ?? 0.0
runTimer(interval: interval)
}
func invalidate() {
timer?.invalidate()
}
func reset() {
startTime = Date.timeIntervalSinceReferenceDate
runTimer(interval: interval)
}
// MARK: Private
private func runTimer(interval: Double) {
startTime = Date.timeIntervalSinceReferenceDate
timer = Timer.scheduledTimer(withTimeInterval: interval, repeats: isRepeatable) { [weak self] _ in
self?.callback()
}
}
}
Usage:
private var playTimer: ResumableTimer?
playTimer = ResumableTimer(interval: 5.0) { [weak self] in
self?.doSomethingOnTimerFire()
}
playTimer?.start()
Short answer: You can't. Timers can't be paused.
Instead you need to record the current time when you start the timer:
let startTime = NSDate.timeIntervalSinceReferenceDate()
let interval = 38.2
//Start your timer
runFirstDropTimer = NSTimer.scheduledTimerWithTimeInterval(interval,
target: self,
selector: "runAnimation",
userInfo: nil,
repeats: false)
Then, when you want to pause your timer, invalidate it and figure out how much time is left:
runFirstDropTimer.invalidate()
let elapsed = NSDate.timeIntervalSinceReferenceDate() - startTime
let remaining = interval - elapsed
And finally when you want to resume your timer create a new timer with the remaining value.