How do I make the app run in the background for once in every 5 mins till the user exits the region.
There’s no direct way to do that. To start, I recommend you read my UIApplication Background Task Notes post, which explains a lot of the background (hey hey!) to this issue.
We start a task with
beginBackgroundTask
, set a timer to executes after 3 minutes. But when the user enters the region, beacon is detected and if the app is in the background, only one request is sent after 3 minutes and it doesn't repeat again till the user exits the region.
The problem here is that background tasks started while you’re in the background have a much shorter time limit. While the exact limit is not documented — it has changed in the past and it may well change again in the future — the current value is about 30 seconds.
So here’s what happens:
The system resumes (or relaunches) your app in the background because the user enters your region.
You schedule a timer for 3 minutes and start a background task to prevent the app from being suspended.
After 30 seconds the background task expires. At this point one of two things happens:
If you have an expiry handler, that will be called. It must end the background task, at which point your app will suspend (A).
If you have no expiry handler, or it fails to end the background task promptly, the watchdog will kill your app (B).
This means that the timer will not run after 3 minutes. Either your app is suspended (which will prevent the timer from running) or your app is terminated.
This state continues until the user leaves your region. At that point one of two things happens:
In case A, the system will resume your app in the background. Your timer is long overdue, so it’ll fire now.
In case B, the system will relaunch your app in the background. As this is a new process, the timer no longer exists.
IMPORTANT If you want to test this, make sure to run your app from the Home screen rather than from Xcode. The Xcode debugger disables the watchdog, prevents your app from suspending, and so on. You can read more advice on this topic, albeit in a different context, in my Testing Background Session Code post.
Oh, btw, I wanted to address a few of your code snippets. To start, this is a concern:
DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async {
self.beginBackgroundTask()
self.countdownTimer = Timer.scheduledTimer(timeInterval: 180, target: self, selector: #selector(self.requestService), userInfo: nil, repeats: true)
RunLoop.current.add(self.countdownTimer, forMode: .common)
RunLoop.current.run()
}
Here you’re consuming a dispatch worker thread in order to run a timer. That’s very wasteful. Dispatch has a limited number of worker threads available to it, and you shouldn’t waste one like this.
If necessary you can use either:
In this case, however, neither of these is a great idea. Consider the thread safety of your
backgroundTask
property. While it’s possible to start and end background tasks from any thread, the background task expiry handler will be called on the main thread. So, if the expiry handler touches the
backgroundTask
property, every reference to that property should be in the main thread [1].
The simplest way to achieve that is to schedule your timer on the main thread. If necessary, it can then dispatch work off to a secondary thread/queue.
And this one is also worrying:
self.backgroundTask = UIApplication.shared.beginBackgroundTask(expirationHandler: {
// you can't call endBackgroundTask here and you don't need to
})
This is wrong. You must start every background task you end, even if the expiry handler is called. Failing to do this will get your app killed by the watchdog.
I’m not sure what caused you to say that “you can't call
endBackgroundTask
here”, but if you post some details about the problem you saw I should be able to explain what went wrong.
Share and Enjoy
—
Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware
let myEmail = "eskimo" + "1" + "@apple.com"
[1] The alternative is to confine every reference to the property to some other thread/queue, but then your expiry handler will need to call that thread/queue synchronously because your expiry handler must guarantee that the background task is ended before it returns.