Something calls a method when the app is in the background - swift

I am using a timer to check certain conditions every minute. Timer code:

func startTimer() {
timer?.invalidate()
timer?.tolerance = 0.2
timer = Timer.init(fire: Day.dateWithNextMinute(), interval: 5, repeats: true) { [weak self] _ in
self?.checkToPass()
}
RunLoop.current.add(timer!, forMode: .default)
}
Everything works correctly.
But there were problems: for some users, the application began to crash when app is in the background, due to an unhandled value in the checkToPass() method (identified by the logs).
I can't figure out how this method can be called when the application is in the background. Is it possible that RunLoop has a side effect and somehow the body of the timer can be called (in the background)? Or maybe it's the timer?
No specific crash time was found. The app was just in the background.


Thanks for any ideas


Related

Swift Timers in Loop all fire at the same time instead of sequentially

I am using a Swift Timer which works fine until I try putting it into a loop. I start the timer and after it reaches zero the selector calls a method to invalidate the timer. I want this to be repeated for 3 times and have a counter that counts the number of iterations this goes through.
func start() {
var interval = 0
repeat {
interval += 1
print ("Interval \(interval)")
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(fireTimer), userInfo: nil, repeats: true)
} while (interval <= 3)
}
#objc func fireTimer() {
timeOn -= 1
if timeOn == 0 {
print("timer done")
timer?.invalidate()
}
}
The output is:
Interval 1
Interval 2
Interval 3
timer done
It seems as if 3 timers are started simultaneously and then the timer.invalidate stops all of them. What I want is to have the timers start independently and run sequentially. Notice the repeat/while loop. Any suggestions appreciated.
You said:
It seems as if 3 timers are started simultaneously and then the timer.invalidate stops all of them.
No. All three timers are running and when you call invalidate you're just invalidating the last one. Each time you set timer, you are discarding your reference to the prior one. Because you discarded your references to your first two timers, you now have no way to stop them. (Add a print statement in fireTimer and you will see the other two continue to fire after you cancel the one timer.) And because you invalidated on timer when timeOn was zero, the other two will keep firing, with timeOn now having negative values, and the == 0 test will never succeed again.
Instead, you could let your timer handler routine to accept a parameter, the timer reference. That way each one would be able to invalidate itself.
E.g.
func start() {
for interval in 0..< 3 { // if you really want three timers, then for loop is easiest
print ("Interval \(interval)")
Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(fireTimer(_:)), userInfo: nil, repeats: true)
}
}
#objc func fireTimer(_ timer: Timer) {
print(Date(), "tick")
timeOn -= 1
if timeOn <= 0 {
print("timer done")
timer.invalidate()
}
}
Now that is exceedingly confusing have multiple repeating timers all updating the same timeOn property. I changed the if test to be <= 0 to address that problem.
It begs the question why you would want multiple repeating timers firing at basically the same time. E.g. every second, timeOn is being reduced by three. Is that really the intent? Generally you would only want one repeating timer.
This process of scheduling a bunch of timers also begs the question of how you will cancel them when the object in question is deallocated. I guess you could keep an array of them, but it seems very convoluted way of doing it. The other approach is to use the block-based timer with [weak self] reference (to prevent strong reference cycle), and then each can check to see if self is nil and if so, invalidate itself:
func start() {
for interval in 0..< 3 { // if you really want three timers, then for loop is easiest
print ("Interval \(interval)")
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] timer in
guard let self = self else {
timer.invalidate()
return
}
self.fireTimer(timer, interval: interval)
}
}
}
func fireTimer(_ timer: Timer, interval: Int) {
print(Date(), "tick", interval)
timeOn -= 1
if timeOn <= 0 {
print("timer done")
timer.invalidate()
}
}
But I am unclear why you would want multiple repeating timers at all.
You said:
I don’t really want 3 timers. I want the timer block to run three times, sequentially.
Then just create a repeating timer that will run three times and then invalidate itself:
weak var timer: Timer? // weak because when you schedule the timer, the RunLoop keeps a strong reference
deinit {
timer?.invalidate() // in case the timer is still running after you dismiss this object/controller
}
func start() {
timer?.invalidate() // in case you accidentally called this previously, cancel any prior timer (before you lose a reference to it)
var counter = 0
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] timer in
counter += 1
if counter >= 3 { timer.invalidate() }
self?.doSomething(counter) // do whatever you want here
}
}
Key things to note:
I used single repeating timer.
I used closure based rendition with [weak self] to avoid the strong reference cycle of the selector-based rendition of Timer.
If you’d like to keep a reference to the timer so that you can invalidate it as soon as the parent object is deallocated, keep your own weak reference to it.
In start, I invalidate any prior timer, which is obviously unnecessary if you make sure that you call start once and only once. But, again, it is a solid defensive programming pattern to invalidate any prior timer before you replace any old reference with a new Timer reference.

swift 3: Watch app: if watch goes to sleep there's a delay between interface controllers

I have a countdown timer interface controller that will, once the timer gets down to 00:00, launch another interface controller. If I keep the watch active until the timer reaches 00:00, then the second interface controller launches as it should. However, if the watch goes to sleep, even if it's active right before the timer reaches 00:00, there will be a delay of several seconds to over a minute before the second interface controller launches.
This defect doesn't appear when running in the watch simulator, just when I'm running on the actual device.
I'm using Xcode 8 and swift 3.
Here's my code from the first interface controller:
// this func will update the countdown timer
#objc private func updateTimer() {
totalNumberOfSeconds += 1
numberOfSeconds += 1
if (numberOfSeconds == numSecondsInMinute) {
numberOfSeconds = 0
}
// only attempt to open the RacingTimer interface if this IC is visible
if (isStillVisible) {
// change to the Racing Timer if the countdown timer hits 00:00
if (totalNumberOfSeconds > originalSecondsTimeInterval) {
// the watch must have gone to sleep when the countdown timer
// hit 00:00, so the total num secs is past the orig timer
// set the numberOfSeconds to total - original to pass to RacingTimer
numberOfSeconds = totalNumberOfSeconds - originalSecondsTimeInterval
// launch the racing timer
WKInterfaceController.reloadRootControllers(withNames: ["RacingTimer"], contexts: [numberOfSeconds])
// destroy the timer and reset the vars
countdownClock.invalidate()
numberOfSeconds = 0
totalNumberOfSeconds = 0
} else if (totalNumberOfSeconds == originalSecondsTimeInterval) {
// launch the racing timer
WKInterfaceController.reloadRootControllers(withNames: ["RacingTimer"], contexts: nil)
// destroy the timer and reset the vars
countdownClock.invalidate()
numberOfSeconds = 0
totalNumberOfSeconds = 0
}
}
}
override func awake(withContext context: Any?) {
super.awake(withContext: context)
// get race and timer data
let numSecs = raceDS.timer * 60
originalSecondsTimeInterval = numSecs
cdt = NSDate(timeIntervalSinceNow: TimeInterval(numSecs))
countdownTimer.setDate(cdt as Date)
countdownClock = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true)
countdownTimer.start()
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
nearestMinuteButtonOutlet.setTitle("will activate") // debug only
didAppear()
}
// set the visible boolean to true
override func didAppear() {
super.didAppear()
isStillVisible = true
nearestMinuteButtonOutlet.setTitle("did appear") // debug only
}
// set the boolean to false
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
isStillVisible = false
nearestMinuteButtonOutlet.setTitle("sleeping") // debug only
}
I'm really at a loss as to why there's a delay if the watch goes to sleep. Any help would be greatly appreciated. TIA.
As of watchOS3 there is no solution to have a Timer running in the background. Timer objects shouldn't be used for precise time measurements on iOS either. On iOS you have the alternative to use a CADisplayLink for accurate timings, however, this isn't available on watchOS3/4.
For measuring time in the background, you should save the current date before the app is going to background and calculate the elapsed time when the app is launched again.
If you simply need the other InterfaceController to be visible by the time the user opens your app, you can use the the method described using dates and you can navigate to your other InterfaceController as soon as the user opens your app again.
If you need some code to execute when the countdown is finished, you should rather schedule a background task, they are the only methods of running code in the background on watchOS at the moment.

ios8 Swift SpriteKit - Pause and Resume NSTimers in swift

I have searched many times on the internet but could not find the answer to this question. I know how to pause and resume NSTimers by using the invalidate functions - timer.invalidate. and I know how to resume them. But I have a SpriteKit game. When I pause my game, I stop everything and the timers. I know that I can stop them using .invalidate but when I invalidate them:
For example lets say I have a 5 second timer that runs continously that spawns one block.
When the timer reaches second 3 of the cycle and when I paused the game, and invalidate the timers. When I resume, Now the timers second goes back to 0 and I must wait another 5 seconds. I want it to continue from where it left off, 3 , and wait 2 seconds for the block to spawn.
blockGenerator.generationTimer?.invalidate()
self.isGamePaused = true
self.addChild(self.pauseText)
self.runAction(SKAction.runBlock(self.pauseGame))
e`
and when I resume it:
blockGenerator.generationTimer = ...
I have to wait another 5 seconds, I want the timer to continue from where it left off
If you can help me, I appreciate it thank you.
There is a way to pause/resume Timer instances, because using repeating timers we know the next fire date.
This is a simple class SRTimer and a protocol SRTimerDelegate
Protocol SRTimerDelegate
protocol SRTimerDelegate : AnyObject {
func timerWillStart(_ timer : SRTimer)
func timerDidFire(_ timer : SRTimer)
func timerDidPause(_ timer : SRTimer)
func timerWillResume(_ timer : SRTimer)
func timerDidStop(_ timer : SRTimer)
}
Class SRTimer
class SRTimer : NSObject {
var timer : Timer?
var interval : TimeInterval
var difference : TimeInterval = 0.0
var delegate : SRTimerDelegate?
init(interval: TimeInterval, delegate: SRTimerDelegate?)
{
self.interval = interval
self.delegate = delegate
}
#objc func start(_ aTimer : Timer?)
{
if aTimer != nil { fire(self) }
if timer == nil {
delegate?.timerWillStart(self)
timer = Timer.scheduledTimer(timeInterval: interval, target: self, selector: #selector(fire), userInfo: nil, repeats: true)
}
}
func pause()
{
if timer != nil {
difference = timer!.fireDate.timeIntervalSince(Date())
timer!.invalidate()
timer = nil
delegate?.timerDidPause(self)
}
}
func resume()
{
if timer == nil {
delegate?.timerWillResume(self)
if difference == 0.0 {
start(nil)
} else {
Timer.scheduledTimer(timeInterval: difference, target: self, selector: #selector(start), userInfo: nil, repeats: false)
difference = 0.0
}
}
}
func stop()
{
if timer != nil {
difference = 0.0
timer!.invalidate()
timer = nil
delegate?.timerDidStop(self)
}
}
#objc func fire(_ sender : SRTimer)
{
delegate?.timerDidFire(self)
}
}
Make your class conform to the protocol SRTimerDelegate and initialize a SRTimer instance with
var timer : SRTimer!
timer = SRTimer(interval: 5.0, delegate: self)
Methods
start() calls the delegate method timerWillStart and starts the timer.
pause() saves the difference between the current date and the next fire date, invalidates the timer and calls the delegate method timerDidPause.
resume() calls the delegate method timerWillResume, creates a temporary one shot timer with the saved difference time interval. When this timer fires the main timer will be restarted.
stop() calls the delegate method timerDidStop and invalidates the timer.
When the timer fires, the delegate method timerDidFire is called.
First, let me say this - it is not possible to do with just NSTimer, there is no inbuilt function to do that (you can build logic around that as the answer from Vadian suggests). BUT.
Why NSTimer is not good idea
Lets stop and think for a little. For game objects and precise spawning, you should never use NSTimer in the first place. The problem is implementation of NSTimer (quoting the docs):
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. If a timer’s firing time occurs
during a long callout or while the run loop is in a mode that is not
monitoring the timer, the timer does not fire until the next time the
run loop checks the timer. Therefore, the actual time at which the
timer fires potentially can be a significant period of time after the
scheduled firing time.
There are other problems with NSTimer but that is out of scope of that question.
Solution
What you can do instead, you should listen to delta time change in each update call
let delta = currentPreciseTime - previousPreciseTime
Now, when you have that delta, you can have your counter : Double, and on each update, you increase counter by delta.
let counter : Double
counter += delta
Now that your "timer" is running properly, you can check with simple condition if your period of time already passed, or do whatever you want with it:
let SPAWN_OBJECT_AFTER : Double = 5.0
if counter > SPAWN_OBJECT_AFTER {
// Do something on fire event
self.spawn()
// This line effectively restarts timer
counter -= SPAWN_OBJECT_AFTER
}
You can easily build your own, very easy timer class to do it. Also! This way you have control over what happens in your update call, which is where the update logic belongs. Timer breaks that model by allowing method execution outside that - it might be intended, but usually is not).
I built a games running in production every day and this is I'd say most common solution for periodic events, as it saves the most resources when used appropriately. Obviously not fitting for everything but definitely fits your need.
Hope it helps!
I don't believe there is a way to pause/resume a NSTimer in the way you are talking about. You must use timer.invalidate() and timer.fire(). However, perhaps you can use an int (that starts at 5 and goes down every second) to keep track of how many seconds the initial timer has before fires again and once the times fires again, make sure the new int value is passed to start the initial timer from the correct point in time.

How to run timer and AI logic in Swift simultaneously

I have an NSTimer() and an AI logic for a board game. The AI logic takes long time to process about 3 to 5 seconds(which is ok). When the program is executing the AI logic, the NSTimer doesn't fire until the AI logic finished it's execution.
This is how i started the timer during the initial stage of the game.
public var timer = NSTimer()
...
let timeSelector:Selector = "timerFired"
if self.useTime {
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: timeSelector, userInfo: nil, repeats: true)
}
I added the AI logic in Update of an SKScene so that when it detects it is his turn, he will start checking for available moves
override func update(currentTime: CFTimeInterval) {
if gameStatus.playerTurn == "AI" && !aiThiking {
aiThiking = true
aIPlayer.aiCheckAvaialbleMoves()
// executeMove
// change Turn
aiThiking = false
}
My question is, is it possible to let the AI logic execute and let the timer still running at the same time?
Your problem seems to be that aIPlayer.aiCheckAvailableMoves() freezes the app as long as it takes to execute, so NSTimer does not work as it should. You can use different threads to avoid this:
dispatch_async(dispatch_get_global_queue(priority, 0)) {
aIPlayer.aiCheckAvaialbleMoves()
}
This code runs this function in a new thread and when the code ended, the thread is closed automatically.
You could use completion blocks to know exactly when the function ended. Here is an example:
func aiCheckAvailableMoves(completion : (result : BOOL) -> Void) {
dispatch_async(dispatch_get_global_queue(priority, 0)) {
//Do your code normally
//When you have finished, call the completion block (like a return)
completion(true)
}
}
And then, from your update: you will get when the function ended (please note here there is no dispatch_async):
aIPlayer.aiCheckAvailableMoves(){
//Finished checking moves, maybe schedule again?
}
Be careful with threads, as they can cause unwanted behaviour or/and crashes!

What is the "cleartimeout" equivalent for Swift?

I'm trying to set up a timeout on a text input field that only implements the inner code a second after the user stops typing. So while the user is typing, I would continually call a cleartimeout and re-initiate the setTimeout.
I was originally looking at the performSelector function in Objective C, but it looks like there is no Swift equivalent for this.
Then I moved on to the GCD functions in Swift, looking for a way to execute this.
Here is what I came up with:
var delta: Int64 = 1 * Int64(NSEC_PER_SEC)
var time = dispatch_time(DISPATCH_TIME_NOW, delta)
dispatch_suspend(dispatch_get_main_queue())
dispatch_after(time, dispatch_get_main_queue(), {
println("timeout")
});
The dispatch_suspend function is not working as I was hoping.
Maybe the dispatch functions are not appropriate here?
You can use dispatch_after rather than one of the performSelector. But I don't think either of these is what you want.
If you are looking to call a block of code only after it's been idle for one second, then I think you want to use a timer (e.g. Timer is fine, or you could use a dispatch timer). Bottom line, every time you get keyboard interaction, see if there is a pending timer, and if so, invalidate it, and then schedule the new one.
So I might be inclined to do something like the following in Swift 3. For example, in iOS 10 and later, you can use the block rendition:
weak var timer: Timer?
func resetTimer() {
timer?.invalidate()
timer = .scheduledTimer(withTimeInterval: 1.0, repeats: false) { [weak self] timer in
// do whatever you want when idle after certain period of time
}
}
Or, if you need to support earlier iOS versions that do not have block-based timers:
weak var timer: Timer?
func resetTimer() {
timer?.invalidate()
timer = .scheduledTimer(timeInterval: 1, target: self, selector: #selector(handleIdleEvent(_:)), userInfo: nil, repeats: false)
}
#objc func handleIdleEvent(_ timer: Timer) {
// do whatever you want when idle after certain period of time
}
If you use this latter approach, though, recognize that this Timer keeps a strong reference to its target, so you might want to cancel the timer in viewDidDisappear (but not deinit). This is why we prefer the block-based rendition or GCD timers.
By the way, I am not sure what your intent of dispatch_suspend was, but don't suspend the main queue. You never want to do anything that could potentially interfere with the main queue's timely processing of events (i.e., never block/suspend the main queue).