If I use this method to run a task, but keep app in foreground, will expirationHandler be called while app is in foreground?
I use this for starting location service everytime I enter background, but sometimes user enters background and immediately returns to app, will this call expirationHandler?
func beginBackgroundTask(expirationHandler handler: (() -> Void)? = nil) -> UIBackgroundTaskIdentifier
Documentation says:
A handler to be called shortly before the app’s remaining background time reaches 0. Use this handler to clean up and mark the end of the background task. Failure to end the task explicitly will result in the termination of the app. The system calls the handler synchronously on the main thread, blocking the app’s suspension momentarily.
https://developer.apple.com/documentation/uikit/uiapplication/1623031-beginbackgroundtaskwithexpiratio
It seems that expiration handler is not called in the foreground mode. After very long waiting I went into background mode and then after ~170 seconds expiration handlers were called for all reported previously background tasks.
Related
In order to identify the app termination, i have implemented the Finite-length task but after some period of time app gets terminated and started from start screen.
func registerBackgroundTask() {
backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
self?.endBackgroundTask()
}
assert(backgroundTask != .invalid)
}
func endBackgroundTask() {
UIApplication.shared.endBackgroundTask(backgroundTask)
backgroundTask = .invalid
if backgroundTask != .invalid {
endBackgroundTask()
}
}
func applicationDidEnterBackground(_ application: UIApplication) {
registerBackgroundTask()
}
app should not be terminated when performing Finite-Length tasks, also please provide examples to identify about the termination of the application when it is in suspended mode.
app should not be terminated when performing Finite-Length tasks
That's not promised at all. There are lots of reasons your app might be terminated. beginBackgroundTask is a request to the OS for more time to finish executing a short-running operation for the user. It doesn't promise your request will be granted.
The specific way you're doing this is likely to fail occasionally, and is specifically addressed in the docs:
Call this method as early as possible before starting your task, and preferably before your app actually enters the background. The method requests the task assertion for your app asynchronously. If you call this method shortly before your app is due to be suspended, there is a chance that the system might suspend your app before that task assertion is granted. For example, do not call this method at the end of your applicationDidEnterBackground(_:) method and expect your app to continue running. If the system is unable to grant the task assertion, it calls your expiration handler.
You want to wrap the specific operation you want to request time for. You always wrap that operation, whether you think you're going into the background or not. You don't just call this every time you go into the background.
please provide examples to identify about the termination of the application when it is in suspended mode.
In applicationWillEnterBackground write a value into UserDefaults. In applicationDidEnterForeground, remove the key. In applicationWillFinishLaunching, look for that key. If it's there, then you're being relaunched, so at some point you died in the background. That includes something like a reboot or power-failure. If it's not there, then this is first launch, or you crashed in the foreground. The precise way to implement this highly depends on what you plan to do with the information.
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.
If I have a function in PerformFetch to do background fetching in iOS.
I have "finally" statement inside the PerformFetch. When the background fetch expired will the finally block get call as well.
Or will it go straight to the ExpirationHandler block to do the clean up before the application killed by the OS
When PerformFetch (application:performFetchWithCompletionHandler:) is called in the background you have ~30 seconds to return from that method or that process will be terminated.
If terminated, your code will never enter the finally block with your PerformFetch override, nor will WillTerminate (applicationWillTerminate:) be called. This will be treated by iOS as an UIBackgroundFetchResult.Failed since PerformFetch did not return.
If you flagged the start of some block of code via BeginBackgroundTask (beginBackgroundTaskWithName:expirationHandler:) within the PerformFetch then yes, the expirationHandler is called when the OS terminates the PerformFetch.
In didFinishLaunchingWithOptions, how do you know if the app is resuming from the foreground or is a new launching? I need to know in this method because there are 2 tasks to run depending on what the launchOptions are and what state the app is in. Thank you.
From Test if app did become active from a UILocalNotification:
When an app enters the foreground from the background it does not trigger applicationDidFinishLaunchingWithOptions. It does, however, call applicationWillEnterForeground and applicationDidBecomeActive. This can be verified with a couple of NSLogs.
So it's possible to know if the app returns from background or if it's a new launch.
From the developer page: search for didFinishLaunchingWithOptions and you will have all the options. Also, they have this diagram telling all the stages that the app has:
What you are looking is for the Inactive State:
The app is running in the foreground but is not receiving events. (It may be executing other code though.) An app usually stays in this state only briefly as it transitions to a different state.
Upon entering this state, the app should put itself into a quiescent state with the expectation of moving to the background or active state shortly.
Or the Suspended State:
The app is in memory but is not executing code. The system suspends apps that are in the background and do not have any pending tasks to complete. The system may purge suspended apps at any time without waking them up to make room for other apps.
After reading this I don't think that what you need is Background State
didFinishLaunchingWithOptions method calls only with a new launch. It can be regular launch in foreground, or it can be app relaunching in background when you use Background Modes. To define the details of launching see the launchOptions.
When app goes into background or foreground see applicationDidEnterBackground and applicationWillEnterForeground methods.
Here is how Branch defines launch options (https://github.com/BranchMetrics/iOS-Deferred-Deep-Linking-SDK/blob/master/Branch-SDK/Branch-SDK/Branch.m) :
- (void)initSessionWithLaunchOptions:(NSDictionary *)options isReferrable:(BOOL)isReferrable explicitlyRequestedReferrable:(BOOL)explicitlyRequestedReferrable automaticallyDisplayController:(BOOL)automaticallyDisplayController {
self.shouldAutomaticallyDeepLink = automaticallyDisplayController;
self.preferenceHelper.isReferrable = isReferrable;
self.preferenceHelper.explicitlyRequestedReferrable = explicitlyRequestedReferrable;
if ([BNCSystemObserver getOSVersion].integerValue >= 8) {
if (![options objectForKey:UIApplicationLaunchOptionsURLKey] && ![options objectForKey:UIApplicationLaunchOptionsUserActivityDictionaryKey]) {
[self initUserSessionAndCallCallback:YES];
}
else if ([options objectForKey:UIApplicationLaunchOptionsUserActivityDictionaryKey]) {
self.preferenceHelper.isContinuingUserActivity = YES;
}
}
else {
if (![options objectForKey:UIApplicationLaunchOptionsURLKey]) {
[self initUserSessionAndCallCallback:YES];
}
}
}
I am trying to keep the iOS app in active state for more than 10 mins when it enters in background state.
How can I implement this.
See "Background Execution" section of the iPhoneAppProgrammingGuide. In short, your app must be one of these types:
Apps that play audible content to the user while in the background, such as a music player app
Apps that keep users informed of their location at all times, such as a navigation app
Apps that support Voice over Internet Protocol (VoIP)
Newsstand apps that need to download and process new content
Apps that receive regular updates from external accessories
And you must add to the Info.plist as follows:
Add the UIBackgroundModes key to your
Info.plist file and set its value to an array containing one or more of the following strings:
audio—The app plays audible content to the user while in the background. (This content includes streaming audio or video content using AirPlay.)
location—The app keeps users informed of their location, even while it is running in the background.
voip—The app provides the ability for the user to make phone calls using an Internet connection.
newsstand-content—The app is aNewsstand app that downloads and processesmagazine or newspaper
content in the background.
external-accessory—The app works with a hardware accessory that needs to deliver updates on a
regular schedule through the External Accessory framework.
bluetooth-central—The app works with a Bluetooth accessory that needs to deliver updates on a
regular schedule through the CoreBluetooth framework
Note that part of the review process will be checking to make sure that your app does what it says it's doing with regard to background processing.
Here's what I've done using beginBackgroundTaskWithExpirationHandler.
Write a method that starts a background task.
Inside that background task, run a NSTimer with a scheduled (non repeating) time that is under 10 minutes. For the purposes of my situation I was using 5 minutes.
Once the NStimer's selector fires, end the background task and then instantly call the method that you wrote earlier to start off another background task.
If you want to schedule methods to run at specific times, you will have to check for them in the background task.
This solution isn't really ideal and is still power hungry but will do what you want.
Edit: Since iOS7, I suggest you read this excellent post. Note that this article was last updated in 2013 and is probably irrelevant now.
Only certain types of apps are allowed to run in the background. See the "Implementing Long-Running Background Tasks" section of this guide.
If you aren't requesting permissions to do background processing you can use UIApplication's beginBackgroundTaskWithExpirationHandler but you cannot get extra time.
This code makes your iOS app run indefinitely in the background. Copy and paste the below methods into a singleton / manager which handles the tasks you need to perform in the background.
// #interface
// Declare Private property
#property (nonatomic) UIBackgroundTaskIdentifier backgroundTask;
//#end
// ...
// Copy into
//#implementation
- (void)setupBackgrounding {
[[NSNotificationCenter defaultCenter] addObserver: self selector: #selector(appBackgrounding:)
name: UIApplicationDidEnterBackgroundNotification
object: nil];
[[NSNotificationCenter defaultCenter] addObserver: self selector: #selector(appForegrounding:)
name: UIApplicationWillEnterForegroundNotification
object: nil];
}
- (void)appBackgrounding: (NSNotification *)notification {
[self keepAlive];
}
- (void) keepAlive {
self.backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTask];
self.backgroundTask = UIBackgroundTaskInvalid;
[self keepAlive];
}];
}
- (void)appForegrounding: (NSNotification *)notification {
if (self.backgroundTask != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTask];
self.backgroundTask = UIBackgroundTaskInvalid;
}
}
You can't. Unless your app uses audio, voip or gps. What you can do is notify the user (via local notifications) that the time is almost up and ask him to open/close the app.
Also if you just need to notify the user, you can use push notifications.
https://github.com/yarodevuci/backgroundTask Check my code here I am using audio player that plays blank wav file Works perfectly on IOS 8 Battery usage around 10% in 24 hour period
How to use:
var backgroundTask = BackgroundTask()
backgroundTask.startBackgroundTask() //Starts playing blank audio file. You can run NSTimer() or whatever you need and it will continue executing in the background.
backgroundTask.stopBackgroundTask() //Stops the task
Warning: Apple will reject this if you try to submit it!
If your App type is not one of VOIP/Audio/Location....(check Background Modes),
or you don't want to specify your App as a background App, you can implement beginBackgroundTaskWithName:expirationHandler or beginBackgroundTaskWithExpirationHandler to ask for more time to run your process in background. You can find the detailed description here
Apps moving to the background are expected to put themselves into a quiescent state as quickly as possible so that they can be suspended by the system. If your app is in the middle of a task and needs a little extra time to complete that task, it can call the beginBackgroundTaskWithName:expirationHandler: or beginBackgroundTaskWithExpirationHandler: method of the UIApplication object to request some additional execution time. Calling either of these methods delays the suspension of your app temporarily, giving it a little extra time to finish its work. Upon completion of that work, your app must call the endBackgroundTask: method to let the system know that it is finished and can be suspended.
Each call to the beginBackgroundTaskWithName:expirationHandler: or beginBackgroundTaskWithExpirationHandler: method generates a unique token to associate with the corresponding task. When your app completes a task, it must call the endBackgroundTask: method with the corresponding token to let the system know that the task is complete. Failure to call the endBackgroundTask: method for a background task will result in the termination of your app. If you provided an expiration handler when starting the task, the system calls that handler and gives you one last chance to end the task and avoid termination.