This question already has answers here:
How to call a function when the app is inactive (e.g. playing music in background)?
Swift 3 - How to make timer work in background
(10 answers)
Closed 2 years ago.
I am trying to execute a simple function that runs a Timer in the background of the app.
In short,
A 10 second Timer starts as soon as app begins
I lock the device after seeing confirmation the Timer has begun in the Foreground
10 seconds later, with the app in the Background, I should expect a log to appear. This currently works in the simulator but not on the device
Full code below. You can also download the app itself to try.
After consulting popular answers such as this (which also contains conflicting answers about whether a Timer actually runs in the background or not), I am not exactly what the authoritative answer is on at the topic.
Any help is appreciated.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var timer : Timer?
if timer == nil {
NSLog("Timer started")
timer = Timer.scheduledTimer(withTimeInterval: 10, repeats: true, block: {_ in NSLog("After 10 seconds show up") //this will not work on a device
//Also, I get the error: Can't end BackgroundTask: no background task exists with identifier 1 (0x1), or it may have already been ended. Break in UIApplicationEndBackgroundTaskError() to debug.
})
}
}
}
It may be confusing, but questions like that actually do answer the question. Bottom line, the app is completely suspended (including timers) when the app enters the background. You can, however, request a little time before it is suspended as outlined in Extending Your App’s Background Execution Time. You’ll see many online references to this being able to allow an extra three minutes of background execution before it is suspended, but in iOS 13 this has been further reduced down to only 30 seconds.
Now, apps that have legitimate need or background execution (e.g. a navigation app, a VOIP app, a music playing app, etc.) can request special background execution modes (see About the Background Execution Sequence, but none of these are intended solely for the purpose of keeping a timer running in the background.
If you want to notify a user at some designated time in the future, use user notification service. But do not just attempt to keep your app running in the background.
By the way, while that link describes the process, their code snippet is misleading. See https://stackoverflow.com/a/23831862/1271826 for a more contemporary rendition.
Related
I'd like to prevent iOS from killing my app after a few minutes.
I've read this thread here on SO: Prevent iOS from killing my app after 3 minutes . It says that if I have no backgroundtasks longer than 3 minutes my app wont be killed. Can someone verify that this is true? Because my background-task is not running longer than 3 minutes and even though my app gets killed after this time.
My background-task is a timer that updates a widget. Heres some code:
self.backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
self?.endBackgroundTask()
//endBackGroundTask looks like this
UIApplication.shared.endBackgroundTask(self.backgroundTask)
self.backgroundTask = UIBackgroundTaskInvalid
//
}
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(self.updateTimer)), userInfo: nil, repeats: true)
.
// at the beginning of the class
var backgroundTask: UIBackgroundTaskIdentifier = UIBackgroundTaskInvalid
.
// in viewWillDisappear
self.timer.invalidate()
if self.backgroundTask != UIBackgroundTaskInvalid {
self.endBackgroundTask()
}
You need to structure your app so that it doesn't require continual execution in the background. As I understand it, your app shows a count down timer and can show the same count down timer in a Today Widget. The approach I would use is follows:
Store the "end date" for the timer in user defaults to share with your widget
When your app is in the foreground, use a Timer to periodically update your UI
When your Widget is being displayed use a Timer in the widget to periodically update its UI
When your app moves to the background, schedule a local notification for the expiration time
When your app moves back to the foreground, you can cancel that scheduled notification if it hasn't yet fired.
Support app restoration for those cases where your app is legitimately terminated (e.g. due to memory pressure or being suspended for a long period)
If you do this then you never need to call beginBackgroundTask. If you do call beginBackgroundTask and don't call endBackgroundTask within 3 minutes of entering the background, then your app will be terminated, even if you aren't using any CPU.
Short answer: You can't run a background task for longer than 3 minutes unless you are a turn-by-turn navigation app or an audio player. Apple doesn't allow it by design.
Your background task is a timer that is running longer than 3 minutes. So your app is correctly being killed. Consider it confirmed as that is Apple's design.
It's not what your timer is executing that is killing the app, it's the timer itself.
You can read up on Apple's Documentation for more information.
Always try to avoid doing any background work unless doing so improves the overall user experience. An app might move to the background because the user launched a different app or because the user locked the device and is not using it right now. In both situations, the user is signaling that your app does not need to be doing any meaningful work right now. Continuing to run in such conditions will only drain the device’s battery and might lead the user to force quit your app altogether. So be mindful about the work you do in the background and avoid it when you can.
I am making an app that helps people with tracking down the working intervals.
What I need is the timer should run at least 30 minutes regardless of the app is in foreground or background.
func startFocus() {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(Pomodoro.focusIntervalCounter), userInfo: nil, repeats: true)
}
func focusIntervalCounter() {
dynamic_focusIntervalSecond -= 1
focusDelegate?.updatingFocusCountingDown(timeStamp: seconds2Timestamp(intSeconds: dynamic_focusIntervalSecond), secondLeft: dynamic_focusIntervalSecond)
if dynamic_focusIntervalSecond == 0 {
timer.invalidate()
focusDelegate?.focusCountingDowndid(end: true)
}
}
focusIntervalCounter() should invalid the timer when the `dynamic_focusIntervalSecond` is 0.
It works fine when the program is in the foreground, but after the screen is shut, the timer just works a little while and stop.
Is this any approach to continue the timer's counting?
Thanks in advance.
No, you can run background tasks for up to 5 minutes, use the background update functionality to trigger app refresh, or use notifications to trigger background actions. There is no way in iOS to guarantee that any code will be run consistently in the background after 30 minutes. Local notifications will enable your code to run after the user selects an action in a notification. Silent push notifications can run some code in the background open receipt, but require an internet connection and are not guaranteed to be delivered.
See this question for more info:
iOS Timer in the background
No, You cannot run Timers in background mode. You can create a UIBackgroundTaskIdentifier which will give you 180sec as i have observed, i'm not sure it might vary with OS version.
You can try scheduling local notifications for 30 mins.
You can enable back ground modes if you're using push notification, Airplay, Location, VOIP apps, Bluetooth, News stand, Background fetch, for more details read this apple developer document BackgroundExecution
Some things are not possible in background, Have you switched your project to enable the background modes? You can reed more about it here
I am working on app where there is an MQTT connection with the server and server is sending some values related to device and UI changes accordingly. But when app is in background user should get local notification that certain values are changed. I know background service are not allowed in iOS but I want to make sure that is that there is no way to achieve this.
I successfully added local notification with app in background by UIApplication.shared.beginBackgroundTask but it's only work for 3 min exact after that apple terminates the app.
func registerBackgroundTask() {
backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
self?.endBackgroundTask()
}
assert(backgroundTask != UIBackgroundTaskInvalid)
}
func endBackgroundTask() {
print("Background task ended.")
UIApplication.shared.endBackgroundTask(backgroundTask)
backgroundTask = UIBackgroundTaskInvalid
}
And just calling self.registerBackgroundTask() makes the app runnable in background for three min.
Next that I am going to try is that background fetch and widget to run service, Here I just want some suggestion that is there any chance that one of above two will work ?
It sounds like "Communicating with an External Accessory" would be the background mode that fits your application.
See Apple Docs for reference.
You have to activate Backround Mode for your project and set the value to "external-accessory". Then you can do ongoing small downloads in background. Apple mentions heart rate monitors as an example.
Please note that continous background polling is waste of energy and would deplete battery quickly. Check if this is really needed for your application. If the user just needs infrequent notifications/alarms, remote notifictions would be a much better solution. I use remote notifications in my own projects and it works very smooth and reliable. Additional benefit is, that it would wake up an app even if the user has closed it before.
For more than 3 Minute. You will be enable any mode. Otherwise when app will enter in background app. After 3 min.App will not perform any action.
In my iOS app I have a task that is executed perodically with a timer (the task is checking my database every 10 seconds):
var timer: dispatch_source_t!;
timer = Dispatch.timerAsync(interval: intervalTime){
//my task here
}
if a speciifc parameter in my database is set then I stop the timer and process the results (including updating the UI) in the main thread:
dispatch_async(dispatch_get_main_queue()) {
self.processResults();
}
Now what I'm trying to do is to make this work when the user switches to another app. When my app goes to the background the timer stops working and when I go back to my app it starts working again. I wanted to know if it is possible to pass this task with the timer to the background thread when the user switches to another app so that my app still checks the database every 10 seconds and if the parameter in the database is set the user gets a notification (local notification). I would like to avoid using Apple's remote notification if possible... Is this possible?
I don't have enough experience with app programming so I wanted to get some idea to see if I'm approaching this problem the right away. Any input, suggestion or sample code is very appreciated. Thanks!
I'm making an app that receives constant updates (potentially hundreds of times a day) and, to make for a better user experience, it would be nice to have these downloaded in the background.
Looking at Apple's[1] documentation I need to set the background mode to "Background fetch". Exploring deeper you can read about the application:performFetchWithCompletionHandler[2] function which states that:
When this method is called, your app has up to 30 seconds of wall-clock time to perform the download operation and call the specified completion handler block... If your app takes a long time to call the completion handler, it may be given fewer future opportunities to fetch data in the future.
The problem is our downloads will take longer than 30 seconds to download, and as such would rather not face the wrath of Apple sending updates fewer and farther between, thus exacerbating the issue!
So, am I able to do this somehow?
Also, I have created a crude experiment whereby I create a NSTimer:scheduledTimerWithTimeInterval to run every minute which logs to the console. This successfully works both on the iPhone in simulation (has been running for 30 mins plus) and also when I place it on a phone (a week plus)... why would this be!?
It may be hard to do because of the Apple 30s obligation. They decided so to eventually prevent big download to happen not to drain battery and data plan.
You must be sure you really need to download that much data (as it takes this long) in background, plus hundred times a day!
I mean, when your app goes foreground after a (long) background period, it may not be updated and it's normal case. So you need to do the update when the app goes foreground; only one update is needed. Otherwise, you should step back and rethink the update process.
Found a solution:
You can bypass Apple's application:performFetchWithCompletionHandler function and set your own timer. To do this make sure you do the following:
Have selected Background fetch under Your app settings > "Capabilities" > "Background Modes".
In AppDelegate.m, within application:didFinishLaunchingWithOptions, add the following code:
[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
// Nothing
}];
You can now add a NSTimer in your AppDelegate and it will continue to run whilst in the background. E.g.,
_timerBg = [NSTimer scheduledTimerWithTimeInterval:1800
target:self
selector:#selector(bgFunction)
userInfo:nil
repeats:YES];