I'm trying to fully understand the below code I put together after researching background tasks in iOS and am hoping for some help,
I understand the basic concept,
First we get the app singleton, then we create a block and register with the system the background task, and then finally we asynchronously dispatch the task to run.
So here are the pieces I'm looking for help with:
When background_task is assigned the block, the actual block does not have the code we want run inside it, only the cleanup code in it's completion handler, why is that?
I understand dispatch_async basically starts a new thread and starts working through the code in the block, but where in this dispatch_async request is the background_task referenced? I don't see how the system understands that the code we want executed in the dispatch_async request is related to the background_task we registered earlier.
Why do we need the cleanup code both at the end of the dispatch_async block and in the completion handler of the background_task?
Sorry if these are stupid questions, but I just don't get the syntax,
Here is the code i've cobbled together:
UIApplication *application = [UIApplication sharedApplication]; //Get the shared application instance
__block UIBackgroundTaskIdentifier background_task; //Create a task object
background_task = [application beginBackgroundTaskWithExpirationHandler: ^ { //Register background_task
[application endBackgroundTask: background_task]; //Tell the system that we are done with the tasks
background_task = UIBackgroundTaskInvalid; //Set the task to be invalid
//Above code called when endBackgroundTask is called
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//Perform your tasks that your application requires
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(updateText) userInfo:nil repeats:YES];
NSLog(#"\n\nRunning in the background!\n\n");
[application endBackgroundTask: background_task]; //End the task so the system knows that you are done with what you need to perform
background_task = UIBackgroundTaskInvalid; //Invalidate the background_task
});
There is no relationship between the background task identifier and the work you do on a secondary thread. The background task represents a request for some extra time to run. That's all. You need to end it to tell the OS that you've completed the work you wanted to do. If you fail to do that in the time available, your app will be terminated. The task ID is just a token representing the permission from the OS to keep working for a bit of time.
You have to clean up in both places, the expiration handler and the end of your async-dispatched block, because they represent two different occurrences. In the block you dispatch to the concurrent queue, you end the task because you've completed your work in time and you want to let the OS know so it can suspend your app; it doesn't need to terminate it. In the expiration handler, that's your last chance to end the task to prevent your app from being terminated. You haven't completed your work, but you've run out of time. If you didn't end the background task at that point, the OS would kill your app.
By the way, scheduling a timer in a task running on a dispatch queue won't work. A timer is scheduled on a thread's run loop. The worker threads which service dispatch queues can be terminated at any time and, in any case, don't run their run loop.
It seem quite long time since this question was asked and answered.
I just would like to make a note here as I have a feeling that the user Woodstock (and may be many of us) a bit confused about "background thread" and "background task".
The "background thread" is considering in the context of UI thread and background thread.
The "background task" is considering in the context of the task allowable to take longer than normal duration (10 seconds in iOS).
The method beginBackgroundTaskWithExpirationHandler: tells iOS that you need more time to complete whatever you’re doing in case the app is backgrounded (please refer to app's life cycle concept). After this call, if your app is backgrounded it will still get CPU time until you call endBackgroundTask: or system signals it's going to be expired.
Just for clarity I'm setting up the background task as follows:
#interface myClass ()
#property (nonatomic, assign) UIBackgroundTaskIdentifier backgroundUpdateTask;
#end
- (void)importantStuffToCompleteInBackground
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
[self beginBackgroundUpdateTask];
// do important background stuff
[self endBackgroundUpdateTask];
});
}
- (void)beginBackgroundUpdateTask
{
self.backgroundUpdateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[self endBackgroundUpdateTask];
}];
}
- (void)endBackgroundUpdateTask
{
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundUpdateTask];
self.backgroundUpdateTask = UIBackgroundTaskInvalid;
}
According to beginBackgroundTaskWithExpirationHandler: docs the block will be called only shortly before the remaining background time reaches 0 in case you didn't complete the important background stuff block of code in time by calling the endBackgroundTask in the dispatch_async.
You might have all kinds of long running background tasks running in your app at any one time. There are several ways you can run a background task.
You could spawn a thread or you could use Grand Central Dispatch to push a block onto a worker queue which might look as simple as..
dispatch_async( queue, ^{
[self saveAllUserDataToServer];
} );
but don't be fooled, anything involving multithreading is difficult and dangerous. It's a big subject and it's not clear from your question if you have a specific task to do that you would like help with.
One such danger is that your app can be stopped by the user or System at any point. A background task you have already started could be half way complete and then just stop. This could be a disaster or just inconvenient.
iOS provides a way to not have these tasks halted abruptly. Anytime you start such a task you must let the system know, and then tell it when the task has finished, like so..
- (void)saveAllUserDataToServer {
UIBackgroundTaskIdentifier bt = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler: ^ {}];
... do the work ...
[application endBackgroundTask: background_task];
}
..now you know it is safe to call -saveAllUserDataToServer on a background thread and it will run to completion even if the app is closed half way through.
The one caveat is that you only have a certain time limit to complete the task.. if you take too long the ExpirationHandler block will be called, and you MUST clean up properly in here. This is way the setup code is more likely to look like..
__block UIBackgroundTaskIdentifier bt = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler: ^ {
.. task didn't complete within time limit.. do additional cleanup
[[UIApplication sharedApplication] endBackgroundTask: background_task];
}];
I was getting the following error when my app went to the background because of a continually running process in the background:
Can't endBackgroundTask: no background task exists with identifier 9, or it may >have already been ended. Break in UIApplicationEndBackgroundTaskError() to debug.
Thankfully I came across hooleyhoop's answer and I was able to get around it with something like this code example:
func run() {
DispatchQueue(label: "ping").async {
let tid = UIApplication.shared.beginBackgroundTask(withName: "ping", expirationHandler: {})
//network code
//save data to database code
UIApplication.shared.endBackgroundTask(tid)
run() //repeat process
}
}
You can also use:
UIApplication.shared.beginBackgroundTask(expirationHandler:{})
if you don't need a nice name when debugging
Related
I have the following code which makes a call to the server, before the app is about to exit. My problem is, that the code sometimes works, sometimes not. Mostly not. Here is the code:
//Set the user as in-active if app is force closed
- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
NSLog(#"called");
bgTask = [application beginBackgroundTaskWithName:#"setInActive" expirationHandler:^{
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(#"disp called");
//If the app is about to exit make the user inactive
[[APIManager sharedInstance] setInActiveOnCompletion:^(BOOL finished){
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
});
}
The first NSLog gets called everytime. However the second one does not. It seems as if the app does not even go into the dispatch_async method.
EDIT: So basically all I need to do is tell the server that a user has force quit the app, while that user was signed in. How could I do this?
Any ideas?
applicationWillTerminate is the last method that get called before app is being terminated!! So, you can't do anything after that, if you want to perform something in background then begin that task when you performing your operation, you can't begin background task from applicationWillTerminate because your app will not be in background after this method call!!
When your application is in the background, it is only allowed very little amount of processing, therefore using dispatch_async will not buy you much extra time. Also, Apple does not want that.
I am trying to transfer a firmware file from my app to a wearable hardware.
it takes about some time and when my app goes in background or the lock button is pressed the firmware transfer process discontinues.
ideally it should continue to transfer the firmware. I am using this method to continue the process in background and also also have declared the support for the background modes.
- (void)applicationDidEnterBackground:(UIApplication *)application {
bgTask = 0;
bgTask = [application beginBackgroundTaskWithName:#"MyTask" expirationHandler:^{
// Clean up any unfinished task business by marking where you
// stopped or ending the task outright.
//[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Do the work associated with the task, preferably in chunks.
//[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
}
This method is not able to do the task.
However if I use this method in Appdelegate - didFinishLaunchingWithOptions it works.
But there is a trade off with putting this piece of code in the above method i.e If I am not transferring the firmware and the app goes in background then after 3 mins this piece of code removes the app and bluetooth connection breaks.
If I am not using this method at all, then the connection remains until it is broken manually but background transfer does not happen.
I have to keep both the operations simultaneously. Please suggest something as I have been for many days on this particular problem.
Thanks in advance.
Try to run your application using BackgroundModes
and remove all the codes from didEnterInbackground
Could someone explain sequence of execution please in applicationDidEnterBackground?
UIBackgroundTaskIdentifier background_task;
background_task = [application beginBackgroundTaskWithExpirationHandler: ^ {
[application endBackgroundTask: background_task];
background_task = UIBackgroundTaskInvalid;
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(#"\n\nRunning in the background!\n\n");
[application endBackgroundTask: background_task];
background_task = UIBackgroundTaskInvalid;
});
My understanding is
create identifier for background task and assign block that will be called once time (10 minutes or so) expires
dispatch async method, output NSLog. During this time all other methods of the app can be used
immediately after NSLog out terminate background task, not waiting for system default expiration
Specifically, after I call NSLog
[application endBackgroundTask: background_task];
background_task = UIBackgroundTaskInvalid;
task will be terminated and expirationHandler block will not be called.
I also think my understanding is incorrect...
Everything about your post is basically correct except for one important detail. None of this has anything to do with the applicationDidEnterBackground app delegate method.
Any task in your app that might take more than a couple of seconds should be wrapped inside calls to beginBackgroundTaskWithExpirationHandler and endBackgroundTask.
The whole point of wrapping code in these two methods is to notify the OS that you have some processing that needs to keep running even if the app happens to enter the background while it is running. Without these blocks, your app will be killed by the OS after only a few (10?) seconds of trying to run in the background.
I have some problems on elaborating a useful strategy to support background for NSOperationQueue class. In particular, I have a bunch of NSOperations that perform the following actions:
Download a file from the web
Parse the file
Import data file in Core Data
The operations are inserted into a serial queue. Once an operation completes, the next can start.
I need to stop (or continue) the operations when the app enters the background. From these discussions ( Does AFNetworking have backgrounding support? and Queue of NSOperations and handling application exit ) I see the best way is to cancel the operations and the use the isCancelled property within each operation. Then, checking the key point of an operation against that property, it allows to roll back the state of the execution (of the running operation) when the app enters background.
Based on Apple template that highlights background support, how can I manage a similar situation? Can I simply cancel the operations or wait the current operation is completed? See comments for details.
- (void)applicationDidEnterBackground:(UIApplication *)application
{
bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
// Do I have to call -cancelAllOperations or
// -waitUntilAllOperationsAreFinished or both?
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// What about here?
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
}
Thank you in advance.
Edit
If NSOperation main method perform the code below, how is it possible to follow the Responding to Cancellation Events pattern?
- (void)main
{
// 1- download
// 2- parse
// 2.1 read file location
// 2.2 load into memory
// 3- import
// 3.1 fetch core data request
// 3.2 if data is not present, insert it (or update)
// 3.3 save data into persistent store coordinator
}
Each method I described contains various steps (non atomic operations, except the download one). So, a cancellation could happen within each of these step (in a not predefined manner). Could I check the isCancelled property before each step? Does this work?
Edit 2 based on Tammo Freese' edit
I understand what do you mean with your edit code. But the thing I'm worried is the following. A cancel request (the user can press the home button) can happen at any point within the main execution, so, if I simply return, the state of the operation would be corrupted. Do I need to clean its state before returning? What do you think?
The problem I described could happen when I use sync operations (operations that are performed in a sync fashion within the same thread they run). For example, if the main is downloading a file (the download is performed through +sendSynchronousRequest:returningResponse:error) and the app is put in background, what could it happen? How to manage such a situation?
// download
if ([self isCancelled])
return;
// downloading here <-- here the app is put in background
Obviously, I think that when the app is then put in foreground, the operation is run again since it has been cancelled. In other words, it is forced to not maintain its state. Am I wrong?
If I understand you correctly, you have a NSOperationQueue and if your application enters the background, you would like to
cancel all operations and
wait until the cancellations are processed.
Normally this should not take too much time, so it should be sufficient to do this:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[_queue cancelAllOperations];
[_queue waitUntilAllOperationsAreFinished];
}
The definition of "too much time" here is approximately five seconds: If you block -applicationDidEnterBackground: longer than that, your app will be terminated and purged from memory.
Let's say that finishing the cancelled operations takes longer than 5 seconds. Then you have to do the waiting in the background (see the comments for explanations):
- (void)applicationDidEnterBackground:(UIApplication *)application
{
bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
// If this block is called, our background time of normally 10 minutes
// is almost exceeded. That would mean one of the cancelled operations
// is not finished even 10 minutes after cancellation (!).
// This should not happen.
// What we do anyway is tell iOS that our background task has ended,
// as otherwise our app will be killed instead of suspended.
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
// Normally this one is fast, so we do it outside the asynchronous block.
[_queue cancelAllOperations];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Wait until all the cancelled operations are finished.
[_queue waitUntilAllOperationsAreFinished];
// Dispatch to the main queue if bgTask is not atomic
dispatch_async(dispatch_get_main_queue(), ^{
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
});
}
So basically we tell iOS that we need some time to perform a task, and when the task is finished of the time runs out, we tell iOS that our task has ended.
Edit
To answer the question in your edit: To respond to cancellation, just check for cancellation whenever you can, and return from the -main method. A cancelled operation is not immediately finished, but it is finished when -main returns.
- (void)main
{
// 1- download
if ([self isCancelled]) return;
// 2- parse
// 2.1 read file location
// 2.2 load into memory
while (![self isCancelled] && [self hasNextLineToParse]) {
// ...
}
// 3- import
// 3.1 fetch core data request
if ([self isCancelled]) return;
// 3.2 if data is not present, insert it (or update)
// 3.3 save data into persistent store coordinator
}
If you do not check for the cancelled flag at all in -main, the operation will not react to cancellation, but run until it is finished.
Edit 2
If an operation gets cancelled, nothing happens to it except that the isCancelled flag is set to true. The code above in my original answer waits in the background until the operation has finished (either reacted to the cancellation or simply finished, assuming that it does not take 10 minutes to cancel it).
Of course, when reacting to isCancelled in our operation you have to make sure that you leave the operation in a non-corrupted state, for example, directly after downloading (just ignoring the data), or after writing all data.
You are right, if an operation is cancelled but still running when you switch back to the foreground, that operation will finish the download, and then (if you programmed it like that) react to cancel and basically throw away the downloaded data.
What you could do instead is to not cancel the operations, but wait for them to finish (assuming they take less than 10 minutes). To do that, just delete the line [_queue cancelAllOperations];.
If we have a long running task, we can ensure that the application doesn't quit until that task is complete by using the following piece of code:
UIApplication *app = [UIApplication sharedApplication];
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
// Clean up any unfinished task business by marking where you.
// stopped or ending the task outright.
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// Do the work associated with the task, preferably in chunks.
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
In my case, the applications plist contains an entry for Application does not run in background set to YES. Now, i have a serial queue (dispatch_queue_create(..., NULL)) that logs the operations and perform them one by one. User can post many tasks at a time and it might take couple of seconds 20-30 for all of them to complete.
Now my question is, can i use beginBackgroundTaskWithExpirationHandler with my serial queue? If so, any recommendations as to how? I believe i'll have to maintain an array of UIBackgroundTaskIdentifier?
To the best of my knowledge, if the application doesn't support background processing, the application quits immediately. So, starting a background tasks is not possible.