Timer is not starting after explicitly telling it to [duplicate] - ios

This question already has answers here:
Why would a `scheduledTimer` fire properly when setup outside a block, but not within a block?
(3 answers)
Swift Timer.scheduledTimer() doesn't work
(2 answers)
Closed 3 years ago.
This is slowly beginning to drive me insane because it doesn't make sense...
I'm using Swift and can't seem to get this timer to start no matter what I do.
I have an NSObject that's handling all my time-related things in my app, and in that NSObject I have a function that initializes a timer. It looks like this:
class Time: NSObject {
static let sharedInstance = Time()
private override init(){}
var roundTimer = Timer()
//This is called from my splash screen while the app is loading
func initializeTimers(){
//Initialize the round countdown timer
roundTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(updateRoundTimerCountdown)), userInfo: nil, repeats: true)
print("The round timer should have started")
}
#objc func updateRoundTimerCountdown(){print("Round Timer ran"); Time.roundTimerIsAt -= 1; print("Round Timer: \(Time.roundTimerIsAt)")}
}
From my splash screen where the app loads all the user data, I call Time.sharedInstance.initializeTimers() in order to start it. I get to the point of it printing "The round timer should have started" to the debugger (which happens after the line that should start the timer) but the selector isn't hit at all. The "Round Timer ran" isn't printed to the debugger nor does the timer appear to have started, so what am I doing wrong?
I appreciate any help, even if the answer is glaringly obvious lol :P I've spent too much time on this!

I actually finally got it working! I'm sorry...there would have been too much code if I posted my entire splash screen view controller and Time NSData, but it appears the issue was elsewhere. But maybe someone else may run into this so this may help.
On my splash screen, I perform 15 'app-loading steps' that include several steps that gather information from servers and such... those steps I progress through by using completion handlers to ensure that the data is actually collected.
It's after collecting some of this data that I call the Time.sharedInstance.initializeTimers() function. My call to the servers runs on a different thread other than the main, so apparently by calling this function after the completion handler runs, it's still on the other thread and these timers can't start on anything other than the main thread!?
So all I did was this:
DispatchQueue.main.async {Time.sharedInstance.initializeTimers()}
And it works now... (facepalm). Doesn't make a whole lot of sense to me but... it works :P

Related

Timer.ScheduledTimer not initialising quickly enough if started on main thread

In the accepted answer for Timer.scheduledTimer not firing, it is emphasised to start the timer on the main thread to ensure that it fires. However, if I do that then I often end up with the timer being slow to initialise, and therefore failing in its purpose as a debouncer. Just wondering if there is something I am doing wrong, or a better way of doing this.
My problem (pseudocode at the bottom):
I use a JWT to authenticate my server calls, and I check this locally to see if it's expired before submitting it. However, I don't want several network calls to notice the expired JWT all at once and submit several refresh requests, so I use a semaphore to ensure only one call at a time is checking/renewing the JWT. I also use a dispatchGroup to delay the original network call until after the checking/renewing is done. However, if the refresh fails I want to avoid all the queued calls then trying it again. I don't want to block all refresh calls forever more with a boolean, so I thought I would create a scheduledTimer to block it. However, if I create it on the main thread, there's a delay before it's created and the released network calls submit a few more refresh attempts before they're blocked.
Questions
Should I just create the timer on the local thread to ensure there's no delay (I presume the main thread is occupied with some UI tasks which is why the timer doesn't get created instantly?)
More generally, is there a better way of doing this? I suspect there is - I tried playing with adding items to a queue, and then cancelling them, but then I began getting worried about creating work items with out of date values of functions, and capturing things in closures etc (it was a while ago, I can't remember the details), so I went with my current bodge.
This might all be easier if I was using await/async, but our app supports all the way back to iOS12, so I'm stuck with nests of completion handlers.
Hopefully this pseudocode is accurate enough to be helpful!
private static let requestQueue: DispatchQueue = DispatchQueue(label: "requestQueue", qos: .userInteractive, attributes: .concurrent)
public static let jwtValidityCheckSemaphore: DispatchSemaphore = DispatchSemaphore(value: 1)
private static var uglyHackTimer: Timer?
#objc private class func clearUglyHackTimer(){
uglyHackTimer?.invalidate()
uglyHackTimer = nil
}
class func myNetworkCall(for: myPurposes){
let group = DispatchGroup()
jwtValidityCheckSemaphore.wait()
if (uglyHackTimer?.isValid ?? false){
jwtValidityCheckSemaphore.signal()
return
}
group.enter()
if jwtIsInvalid(){
refreshJWT(){success in
if !success{
DispatchQueue.main.async{
self.uglyHackTimer = Timer.scheduledTimer(timeInterval: TimeInterval(2), target: self, selector: #selector(clearUglyHackTimer), userInfo: nil, repeats: false)
}
}
group.leave()
jwtValidityCheckSemaphore.signal()
}
}else{
group.leave()
jwtValidityCheckSemaphore.signal()
}
// Make the original network call
newNetworkRequest = DispatchWorkItem{
// Blah, blah
}
group.notify(queue: requestQueue, work: newNetworkRequest)
}

Multithreading - DispatchQueue asyncAfter delay in Swift

I am using the following code to invoke a function periodically every second. The problem is delay is actually 1.1 seconds and gets drifted more and more eventually as can be seen in NSLogs (and it is visible in other parts of the code as well apart from NSLog). Am I doing it wrong, or should I be using timer?
private func updateTimeCode() {
NSLog("Updating time")
//Some more code that doesn't take time
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.0) {
[weak self] in
self ? .updateTimeCode()
}
}
019-08-06 17:15:19.713234+0530 MyApp-Swift[8299:2685215] Updating time
2019-08-06 17:15:20.812652+0530 MyApp-Swift[8299:2685215] Updating time
2019-08-06 17:15:21.913188+0530 MyApp-Swift[8299:2685215] Updating time
2019-08-06 17:15:23.028814+0530 MyApp-Swift[8299:2685215] Updating time
It's because of the part that you say some more code not relevant taking time. They take time and time passes between each invocation of asyncAfter so basically .now() becomes something more than exactly 1 second ago.
Anyway, it's not a conventional way to achieve it. You need to use timer for that purpose. Here's a useful tutorial about how to use it. Timer in Swift

Timer Loops - not NSTimer - Swift

There are lots of questions about using NSTimer for timer functions but I am instead using a CocoaPod "Countdown Label" https://github.com/suzuki-0000/CountdownLabel
My question/problem specifically is how to trigger an action/notification once it is complete I don't believe this to be the same as the many "NSTimer" questions/tutorials but please correct me if I'm wrong!
I've read the Timer documentation here https://developer.apple.com/documentation/foundation/timer but, again, don't see how it would apply to a 'custom timer library' like this Cocoapod.
I am currently trying this....
func testTimer() {
timerLabel.countdownDelegate = self
timerLabel.start()
if self.timerLabel.isFinished {
print("timer it's finished")
} else {self.timerLabel.start()
}
}
testTimer() is then called when an IBAction is pressed to star the timer.
My logic/thinking was that this function starts the timer, then checks if it finished. If it isn't finished it starts/continues the timer (timerLabel.start) and checks again basically in a loop until it is finished and then prints "timer is finished" but this isn't working and I'm not sure why? (but builds fine)
I know I could just scrap the pod and follow a Timer() tutorial but I'm trying to learn/understand how this sort of 'internal notification' for non Apple libraries would work generally at the same time as solving this specific problem. I hope this all makes sense.
NB. Some of the questions/answers I've read through that I don't believe are what I need are How to check if a NSTimer is running or not in Swift?, Check if Timer is running, Perform segue when timer is finished but they all use the Apple "Timer()".
NB. I am setting up the timer is ViewDidLoad with data from a Segue like this :
let timerLabelTime = Int(selectedWorkoutTime)
timerLabel.setCountDownTime(minutes: Double((timerLabelTime)!*60))
timerLabel.countdownDelegate = self
timerLabel.pause()
which works fine

Why is my swift code pausing at the loading screen?

if timerRunning == false{
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("counting"), userInfo: nil, repeats: true)
timerRunning = true
}
while timerLabel.text != "0"{
gameViewStillRunning = false
}
if gameViewStillRunning == false{
self.performSegueWithIdentifier("segue", sender: nil)
}
The purpose of this code is to display a label counting down, and then when it hits 0, the scene should switch to a different ViewController. This code doesn't get any errors but when I run it, the program does not get any further than the loading screen. Any suggestions?
It looks like you are running a while loop on the main thread which is also the thread responsible for drawing the UI. As long as that thread is stuck in your while loop there's no way for it's run loop to continue and no opporunity for it to update the display or respond to user interaction.
In addition if you look at the NSTimer documentation you might notice that scheduledTimerWithTimeInterval states that it Creates and returns a new NSTimer object and schedules it on the current run loop in the default mode. That timer is scheduling a call to your counting function in the main thread's run loop but since that thread is stuck spinning forever in your while it will never have a chance to execute so it has no chance to update that timerLabel.text.
Rather than attempting to block execution with your while loop while polling for some condition, a better solution would be to allow the run loop to continue and to react when the timer calls your counting function. Let your application react to events (like the time remaining changing) regardless of when or how they happen rather than trying to control the exact sequence of execution.
The problem is that this is an infinite loop:
while timerLabel.text != "0"{
gameViewStillRunning = false
}
The body of the loop doesn't change the value of timerLabel.text, so the condition (the test in the first line) keeps failing, and the loop just loops forever — and the code, and so the entire app, just hangs at that point, coming to a dead stop permanently.

Is there a bug with simulator when we trying to make a timer with timeInterval < 0.1 second?

I'm making a new stopwatch Application with Watchkit, first my application is very simple like this:
first of all i tried to make a playButtonPressed to start a timer:
#IBAction func playButtonPressed() {
println("playButton pressed")
timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: Selector("updateTimer"), userInfo: nil, repeats: true)
startTime = NSDate()
}
with my updateTimer function like this:
func updateTimer() {
duration = NSDate().timeIntervalSinceDate(startTime)
println("updateTimer: \(dateStringFromTimeInterval(duration))")
timeLabel.setText(dateStringFromTimeInterval(duration))
}
the dateStringFromTimeInterval function can help me make a dateString with duration is TimeInterval variable.
every thing is ok with my output on Debug area, i can see the dateString at printOut. But my label is lagging for setting the timeLabel as you can see here:
I don't know why, can any one can help me fix that or may be it is a bug of apple watchkit right now? i don't know it'll be lag on real device or not.
Thanks
You have several good questions in here. Unfortunately I have nothing but bad news for you. I have been working extensively with WKInterfaceTimers over the past couple of weeks and they have severe limitations and bugs associated with them. I have a couple responses broken down here in detail.
Issue 1 - Using a WKInterfaceDate as a Timer
This is going to be really frowned upon by Apple and I wouldn't doubt this would be possible grounds for rejection. As #mattt mentions, you don't want to use an NSTimer to flip the date value. Each time you try to switch the date label, Apple lumps all those changes together and pushes them from the Extension on the iPhone to the Watch over WiFi or Bluetooth. It does this to try to optimize battery life.
Because of this, you will never be able to accurately display the time on the Watch in the way that you are currently doing. The proper way to do this is to use a WKInterfaceTimer.
Issue 2 - Using a WKInterfaceTimer
While WKInterfaceTimers are built to do exactly what you want, they have some limitations. The major one for your use case is that it only does up to second precision, not millisecond. Secondly, the timers are extremely inaccurate. I've seen them anywhere from 50ms to 950ms off. I've followed this radar to bring the issue to Apple's attention.
In summary, your current implementation is not going to be super accurate and will be frowned upon by Apple, and the WKInterfaceTimer is extremely inaccurate and can only perform second precision.
Sorry for the downer answer. :-(

Resources