In my app using iOS 9.2, Swift 2.1 I need to save some data into core data when the app goes to background. For this I registered each of the view controllers in the call path for UIApplicationDidEnterBackgroundNotification notification, with an instance method each for saving respective data.
I read on multiple places that by default the app gets about 5 seconds to finish off the execution and hence we need to use beginBackgroundTaskWithExpirationHandler to extend it to about 5 minutes. Following is an example of the selector method that responds to the above notification.
func applicationEntersBackground()
{
print("Before Extension: \(UIApplication.sharedApplication().backgroundTimeRemaining)")
let taskID = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler(nil)
print("During Extension: \(UIApplication.sharedApplication().backgroundTimeRemaining)")
saveCoreData()
if(taskID != UIBackgroundTaskInvalid)
{
UIApplication.sharedApplication().endBackgroundTask(taskID)
}
print("After Extension: \(UIApplication.sharedApplication().backgroundTimeRemaining)")
}
Following is the results of print() statements
Before Extension: 179.933103708318
During Extension: 179.930266333336
After Extension: 179.922843541659
My doubts are
Why is the remaining time about 180 seconds even before I requested for time extension? I tried multiple times. It is always close to 180 seconds and not the 5 seconds as suggested.
Why doesn't the call to beginBackgroundTaskWithExpirationHandler have any impact on the remaining time?
Once the applicationEntersBackground method of a VC returns, similar notification is sent to another VC's corresponding method. Suppose 180 seconds is the total extended duration and VC1 spends about 10 seconds on notification handling, does VC2 notification handler get around 170 seconds between its beginBackgroundTaskWithExpirationHandler - endBackgroundTask calls?
Between successive invocations of the notification handlers of different VCs, there is obviously a very short period where the extension request is not active. How does the timing play out in this case? Does the 5 second counter (provided it is true) come back to life as soon as an endBackgroundTask call is made, and possibly terminate the application before the next VC can get its notification?
Appreciate any help.
By looking at the documentation for backgroundTimeRemaining:
While the app is running in the foreground, the value in this property remains suitably large. If the app starts one or more long-running tasks using the beginBackgroundTaskWithExpirationHandler: method and then transitions to the background, the value of this property is adjusted to reflect the amount of time the app has left to run.
To answer your questions:
backgroundTimeRemaining stays around 180 while the application is in foreground so you can tell what time you'd have once you start a background task. This value is not an indicator of how long are you allowed to run without a background task.
beginBackgroundTaskWithExpirationHandler has an impact, as you can see, the remaining time decreased (by a small value as the method doesn't take much time)
What matters here is the time passed between the call to beginBackgroundTaskWithExpirationHandler and the one to endBackgroundTask. You can split whatever you need the time interval between your calls, providing you don't exceed the 180s limit
Once you call endBackgroundTask the application will be suspended, regardless it took 2 seconds or 179 seconds.
You can find out more details about application entering background here. I'd recommend going through the documentation, it might clarify other questions you might have on this matter.
Related
I'm trying to complete a task in the background using UIApplication.shared.beginBackgroundTask but for some reason the expirationHandler is never called. The task I'm trying to complete is a video export from photo library but sometimes the export cannot be completed in time while the user is using the app in the foreground.
This is the code I'm using :
func applicationDidEnterBackground(_ application: UIApplication) {
if backgroundTask == .invalid && UploadQueue.instance.hasMoreWork() {
backgroundTask = UIApplication.shared.beginBackgroundTask(withName: "ExportQueue") {
NSLog("DriveLog - System is requesting end. Still more work to do ...")
self.endBackgroundTask()
}
print("Invalid? \(backgroundTask == .invalid)")
NSLog("DriveLog - Starting background task: %i", backgroundTask.rawValue)
}
}
func endBackgroundTask() {
NSLog("DriveLog - End called")
UIApplication.shared.endBackgroundTask(backgroundTask)
backgroundTask = .invalid
}
I'm also calling :
(UIApplication.shared.delegate as! AppDelegate).endBackgroundTask()
during my task if I finish it earlier.
However I never see my expirationHandler being called in the log.
I have also tried putting beginBackgroundTask when starting the task in foreground but I get a warning message about task expiration while being in foreground.
You have not understood what the expiration handler is. It is called only if your time expires. Hence the name.
As soon as you call begin, start your task in the next line (not in the expiration handler). And when you are finished, call end.
You thus need to end the background task in two places: in the expiration handler, and outside it after actually performing your task.
It is very important to call end in both places, because if you fail to do so, the system will decide that you are a bad citizen and will never grant you any extra background time at all.
So, this is the diagram of the flow you need to construct:
Also note that this has nothing to do with UIBackgroundModes. That's a totally different mechanism.
matt's answer covers everything. I'm just going to try to give the same answer in different words because your edit suggests that matt's answer wasn't clear to you. (Read it again, though, it really does cover everything I'm going to say here, just in different words.)
You should not call beginBackgroundTask in applicationDidEnterBackground. You call it when you start whatever task you want time for. In your example that's going to be somewhere inside of UploadQueue. You don't call beginBackgroundTask when going into the background. You call it when you're starting a task that you would like to finish even if you go into the background.
Background tasks generally do not belong to the UIAppDelegate. They belong to the thing that creates the task (in your case: UploadQueue). You can create all the background tasks you want. They cost almost nothing. It's not just one "I want background" at the app level. Read matt's flow chart closely.
It's unclear from your question why you expect the expiration handler to be called. Do you expect your task to task to take so long that the OS forces you to stop it? That's what the expiration handler is for. If you've built your system correctly, it should rarely be called. Your task should end long before it's expired.
For full docs on how to do this, see Extending Your App's Background Execution Time. In particular note the caution:
Don’t wait until your app moves to the background to call the beginBackgroundTask(withName:expirationHandler:) method. Call the method before performing any long-running task.
I have a little card playing app. When the computer is playing, some functions are being fired after some time to mimic a real player, like so:
self.operation.addOperation {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+2) {
self.passTurn()
}
}
Everything works fine, but when I want to cancel the game and start a new one, the app crashes if I do it within two seconds before the self.passTurn() function fires.
Ideally I would like to have some sort of pause between the different operations. The above mentioned operation gets released from the queue immediately after it fires the delayed function, so cancelling or suspends the operation does not work.
Is it possible to somehow retain the operation in the queue for two seconds and release it afterwards when the actual function is done?
Thanks!
I would like to know how DispatchQueue.asyncAfter(deadline:execute:) behaves in case of going to background. I tried to find more information in documentation, but it's nothing there.
Let's assume case in which I invoke:
DispatchQueue.main.asyncAfter(deadline: .now() + 60.0) { ... }
What will happen if I go to background after let's say 10 seconds.
Is it going to be finished in background mode?
If not, let's assume that I will reopen the application after 2 minutes. Will it invoke the closure immediately or after remaining 50 seconds?
The most interesting part to me is to know when the timer counting down 60 seconds stops and resumes.
Update
Looking for differences between DispatchQueue.asyncAfter(deadline:execute:) and DispatchQueue.asyncAfter(wallDeadline:execute:) I've found this information from Apple Staff (source), which actually answers the question:
By the way, do you know why those two distict types are provided in Swift Dispatch library?
These model the difference between wall time (returned by gettimeofday) and Mach absolute time (returned by mach_absolute_time). The latter is independent of system clock changes but stops when you sleep. In the C API everything is flattened to Mach absolute time, which means you lose critical info. Consider what happens if you schedule a timer for 1 second in the future and then the system sleeps for 2 seconds.
To make it clear DispatchQueue.asyncAfter(deadline:execute:) uses absolute time and DispatchQueue.asyncAfter(wallDeadline:execute:) uses gettimeofday (wall time) according to this.
Here another source: What is the difference between dispatch_time and dispatch_walltime and in what situations is better to use one or the other?
Test
Based on that I prepared a test to confirm it:
override func viewDidLoad() {
super.viewDidLoad()
self.textView.text.append(
"expected times: \n1. \(Date().addingTimeInterval(60.0 * 2))\n" +
"2. \(Date().addingTimeInterval(60.0 * 3))\n" +
"3. \(Date().addingTimeInterval(60.0 * 5))\n\n")
DispatchQueue.main.asyncAfter(deadline: .now() + 60.0 * 2) {
self.textView.text.append("\n\(Date()) fired based on ABSOLUTE time (1)!")
}
DispatchQueue.main.asyncAfter(deadline: .now() + 60.0 * 3) {
self.textView.text.append("\n\(Date()) fired based on ABSOLUTE time (2)")
}
DispatchQueue.main.asyncAfter(deadline: .now() + 60.0 * 5) {
self.textView.text.append("\n\(Date()) fired based on ABSOLUTE time (3)!")
}
DispatchQueue.main.asyncAfter(wallDeadline: .now() + 60.0 * 2) {
self.textView.text.append("\n\(Date()) fired based on WALL time (1)!")
}
DispatchQueue.main.asyncAfter(wallDeadline: .now() + 60.0 * 3) {
self.textView.text.append("\n\(Date()) fired based on WALL time (2)")
}
DispatchQueue.main.asyncAfter(wallDeadline: .now() + 60.0 * 5) {
self.textView.text.append("\n\(Date()) fired based on WALL time (3)!")
}
}
Result
I run it on real device with detached debugger and locked the phone. After 3.5 minutes when I restored the application there was:
fired based on WALL time (1)!
fired based on WALL time (2)!
These two events fired exactly when I restored the application. Which proves the statement above. Events based on absolute time appeared later which confirms that their timer had stopped.
A timer started in the foreground will run in the background, if the app is already running in the background for some other reason (e.g. it is playing music and has the background mode for audio turned on, or it is performing background Core Location and has background mode for Core Location turned on).
Otherwise, it will pause in the background.
In short, when the app is suspended (when the user goes to another app), all execution stops and this timer won’t fire unless you’ve configured it for background execution. See App programming Guide for iOS: Background Execution.
As an aside, be wary of testing this from Xcode because being attached to the debugger can change the app life cycle.
It depends how your application configured the background modes. For example if you had background modes enabled and setup location monitor to always, the app can survive in background forever if user did not closed ip. So in this case the DispatchQueue.asyncAfter callback will be called.
On the other hand if background mode is disable the app will enter in freez state when is put on background, so your callback will not be called. In my tests I had notice the following flow: the dispatch queue will not stop, so if you schedule a dispatch in 60 seconds and you stay in background for 100 seconds when will come back to foreground the callback will be called immediately. If you stay in background only 30 seconds then when you will came back you will had to wait 30 more seconds.
Note you can still had background modes disabled and keep with app alive up to 3 minutes in the background if used the background task.
self.backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
// called when the app is about to enter in freez state
[[UIApplication sharedApplication] endBackgroundTask:weakSelf.backgroundTask];
}];
To provide some context, the application I'm working on allows the user to record a series of events along a timeline. One important piece of feedback we received from beta testers was that when the user exits the app while recording they'd like the timer to keep up to date for a set amount of time.
For example if the user has recorded 5 minutes and then leaves the app to check an email for 20 seconds, the timer should be 5:20, but it was actually still 5 minutes.
So we've fixed that - the app now would now show 5:20, and this works in the background.
However we can't figure out a way to stop it after a certain amount of time. A user might want the timer to resume if the app is in the background for a short amount of time, but if you leave it in the background for 30 minutes, the timer will update, and I'd like to be able to give the users an optional cut-off time so that the app pauses after a set amount of time.
I've tried listening out for UIApplicationDelegate notifications, but the problem is I'm looking for a point between applicationDidEnterBackground: and applicationWillTerminate, but there's sadly nothing to cater for my needs there.
To summarise, I'd like to have a grace period of 30-60 seconds from applicationWillResignActive: to determine whether or not to pause the timer, or let it keep going.
Would this be possible?
Thanks in advance
It's generally a bad idea to assume your app will be running in the background.
A better way to think about it IMO would be to set a variable to the current time in applicationDidEnterBackground: and check the time interval to now in applicationWillBecomeActive: :
If the interval is bigger that your grace period, have the time backup to when the app was paused.
If it is smaller, update it as if the app was never in the background.
Use dispatch_after to execute a block of code after a certain number of second:
int64_t delay = 30.0; // In seconds
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, delay * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0ull), ^(void){
//DO SOMETHING
});
Swift 3.0 Xcode 8.0 Code sample:
Using delegate methods : applicationWillBecomeActive & applicationWillResignActive to capture the time in between.
OR
let deadlineTime = DispatchTime.now() + .seconds(30)
DispatchQueue.main.asyncAfter(deadline: deadlineTime) {
alert.dismiss(animated: true, completion: nil)
}
Hope this helps.
this is how I start a background task when application goes background ,
void applicationDidEnterBackground:(UIApplication *)application
{
btId = UIBackgroundTaskInvalid;
UIApplication* cuiApplication = [UIApplication sharedApplication];
void (^backgroundTimeRemainingExtenderHandler)() = ^() {
NSTimeInterval timeRemaining1 = [cuiApplication backgroundTimeRemaining];
if(btId != UIBackgroundTaskInvalid){
[proximityEngine StopEngine];
[cuiApplication endBackgroundTask:btId];
btId = UIBackgroundTaskInvalid;
}
};
btId= [cuiApplication beginBackgroundTaskWithExpirationHandler:backgroundTimeRemainingExtenderHandler];
if(bgmanager != nil){
[bgmanager BeginBackgroundTaskMainLoop];
}
}
My problem is that when my background task calls :
NSURLConnection sendSynchronousRequest
The expiration block is being called even though there is more time remaning , how can I prevent this ?
Regards ,
James
EDIT :
After reading the answer below : I still have 596 seconds left when querying the amount of time left and yet still IOS calls the expiration block handler.
beginBackgroundTaskWithExpirationHandler: is the means by which apps request a little extra background time to do some tidying up as a result of going into the background. However iOS reserves the right to decide how much time it will offer you, if any at all, and to kill your process if you fail to end within the required amount of time.
You don't get to execute in the background indefinitely and you don't get to pick your own time limit. You can query what you've been allocated via backgroundTimeRemaining but that's pretty much the full extent.
Per the documentation the handler is called "shortly before the application’s remaining background time reaches 0". So you should expect backgroundTimeRemaining not quite to be zero.
That being said, if your URL connection hasn't yet completed then you're just meant to note somewhere that it didn't complete and deal with the error next time you come back from the background, usually by trying again. That's what your expiration handler should do, and it needs to do it fast.
The extra time allotted to your app is non-negotiable however.
In my particular case - which I do not quite sure why it behaved the way it behaved , I performed the task on a different thread than the beginbackgroundtask thread , after sendsync returned in that thread the backgroundtask was interrupted by the OS .
When calling sendsync in the beginbackground original thread , it does not occur.
Not sure if its something logical and I did something wrong or an OS bug.