I very simply want something to run
- every say 20 seconds
- simply only when the app is foreground (explicitly not run when background)
- obviously, it would be more elegant if you don't have to bother following the app going in and out of foreground
- The issue, I was amazed to learn with
scheduleRepeating
that, in the simulator, it keeps running when the app is in background: that doesn't give me confidence that it explicitly won't run in background on (some? whatever?) devices - The issue, I discovered that on a device, testing with as many generations / OS as possible,
scheduleRepeating
annoyingly will (in some? all? cases) run one time only when the app goes into background. That is to say: it runs "one more time" once the app goes to background. Suck.
So to repeat: in iOS how to have a simple service that runs every 20 seconds, and, only runs when the app is in a foreground, and, it is fuss-free .. ideally you don't have to restart it, check it or anything during the life of the app...
really what is the best way to do such a simple thing?
This is my (seemingly) working solution, which is inelegant. Surely there is a built-in way, or something, to do such an obvious thing in iOS??
open class SomeDaemon {
static let shared = SomeDaemon()
fileprivate init() { print("SomeDaemon is running") }
var timer: DispatchSourceTimer? = nil
let gap = 10 // seconds between runs
func launch() {
// you must call this from application#didLaunch
// harmless if you accidentally call more than once
// you DO NOT do ANYTHING on app pause/unpause/etc
if timer != nil {
print("SomeDaemon, timer running already")
return
}
timer = DispatchSource.makeTimerSource(flags: [], queue: DispatchQueue.main)
timer?.scheduleRepeating(deadline: .now(), interval: .seconds(gap))
timer?.setEventHandler{ self. doSomeThing() }
timer?.resume()
}
private func doSomeThing() {
if UIApplication.shared.applicationState != .active {
print("avoided infuriating 'runs once more in bg' problem.")
return
}
// actually do your thing here
}
}
@Dopapp's answer is pretty solid and I'd go with it but if you want it even simpler you can try something with the RunLoop:
This will even give you some functionalities about background execution.
WARNING: Code not tested!
Behind the scenes:
It isn't gorgeous, but it works pretty darn well.
This code above is ready to use as-is. Of course you can
timeInterval
to whatever time interval you want.Usage is simple:
In AppDelegate.swift, have these two methods:
Then, have whatever class you want to 'listen' for the timer implement
ForegroundTimerDelegate
, and it's required method,timeDidPass()
. Finally, assign that class/self
toForegroundTimer.sharedTimerDelegate
. For example:Hope this helps!