42

In my app, I am uploading images to server from iPhone. While sync if user press home button, App will close.

I want app must be running in background till sync finish.

My question is: How can I add currently running task with "beginBackgroundTaskWithExpirationHandler"?

Please share your ideas.

elp
  • 8,021
  • 7
  • 61
  • 120
Vardhan
  • 965
  • 2
  • 11
  • 21
  • [These notes](https://developer.apple.com/forums/thread/85066) by an Apple Engineer are a MUST see. – mfaani Feb 21 '22 at 17:03

6 Answers6

110

Despite its name, beginBackgroundTaskWithExpirationHandler: does not actually "begin" a task. It might be better thought of as "register..." rather than "begin...." You're just telling the system that you're in the middle of doing something that would like to complete if that's ok.

Several points:

  • In almost all cases, you want to call beginBackgroundTaskWithExpirationHandler: when you start doing the thing you want to do, and then endBackgroundTask: when you're done. You almost never call these at the point that the user presses the Home button (unless that's the point when you start the task, such as saving something to the server).

  • You can have as many "background tasks" as you want open at a time. They cost nothing. They're like retain counts. As long as you still have one open (a "begin" that did not get to its "end"), then the system will consider that a request for more time. There is very little cost to calling the "begin" and "end" methods, so use these to bracket anything that you want extra time to finish.

  • There is no way to be certain that you will get to finish your sync. These methods just make a request. The OS may still deny that request. The OS may still kill you anytime it needs some extra resources. The OS is not infinitely patient; if you take too long (about 10 minutes usually), it'll kill you anyway after calling your expiration handler. The OS may kill you because the phone is being turned off. Your app must be able to deal with not getting to finish. The OS just gives you this ability so that you can improve the user experience.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • 21
    Your answer is completely awesome, thanx man. Just one update: since iOS 7 you have only 3 minutes for background tasks, not 10 anymore. – n0_quarter Feb 24 '15 at 09:13
  • is it advisable to use beginBackgroundTaskWithExpirationHandler with scanning bluetooth devices on app background? – Jongers Oct 24 '16 at 06:43
  • Bluetooth background scanning is generally better done by registering with the background mode bluetooth-central. Scanning generally doesn't fall in the "just need a little more time to finish this" camp. – Rob Napier Oct 24 '16 at 12:55
  • Other than writing your backgroundTasks inside the `applicationDidEnterBackground` is there **any other place** where you might want to right them? I can understand the syntax. Just can't understand how **you'd know** "oh no! The task isn't finished yet" and where you'll need to put it all together. – mfaani Jul 19 '17 at 16:50
  • or is that you you just *wrap* any important download inside a backgroundTask? Later if the app ever went to background you'd still be able to download in the background and if it didn't go background it would just download it as a normal download? – mfaani Jul 19 '17 at 18:15
  • 1
    The latter. Always wrap any operation that you would want to continue in the background if necessary in an `beginBackgroundTaskWithExpirationHandler` block. That said, you should rarely need to do that for downloading. URLSession's background sessions are much more powerful. – Rob Napier Jul 19 '17 at 18:31
  • Thank you so much! Why not just use [background sessions](https://www.ralfebert.de/snippets/ios/urlsession-background-downloads/) for every task? Is there any overhead? The only diff I can see is that for observing the downloads you can't use completionHandlers and you must use delegates. Not sure how much of a difference that would make – mfaani Jul 19 '17 at 20:19
  • 1
    For regular REST calls, background sessions are much less convenient, and generally not what you'd want. They're not a general purpose tool for every request; they're for performing uploads and downloads. – Rob Napier Jul 19 '17 at 20:43
  • @RobNapier Thanks. I already deviated too much. So I asked a new question about [why it's less convenient?](https://stackoverflow.com/questions/45219562/what-is-the-proper-use-case-for-nsurlsessions-background-sessions). – mfaani Jul 20 '17 at 15:50
  • 1
    Took me a while to figure this out. IMHO "then the system will consider that a request for more time" is misleading. That is: You're implying: if you have 2 backgroundTasks you get 2 * 180 seconds, but that's just incorrect. It means that if you have a background task backgroundTask1 started at second 0 and backgroundTask2 at second 140 and then backgroundTask1 finishes at 160...your backgroundTask2 will still have to end at **180**. It won't be allowed to expand the remainingTime to end at 140 + 160... **180** is ALL you get. – mfaani Aug 02 '17 at 21:58
  • @RobNapier, thanks for the answer, can you tell me is it necessary to call method endbackgroundtask – Amit Thakur Mar 25 '19 at 10:16
  • @AmitThakur Yes. Every begin is required to be balanced with an end. – Rob Napier Mar 25 '19 at 13:05
  • @RobNapier. Thanks again for your reply, actually what issue I am facing is, if any API is getting called and putting the app in background mode, the API got successfully called and it also don't kill the app, but let suppose at login screen I put the app in the background mode and after few minutes I getting the app in foreground mode, I don't see the login screen, the app starts from splash screen. – Amit Thakur Mar 26 '19 at 14:05
  • yeah, if you don't call "end" when you're supposed to, the app will be killed and will relaunch fresh. If you're having trouble, you should open a new question with details about what you are doing, what you expect to happen, and what does happen. – Rob Napier Mar 26 '19 at 14:08
18

I used below code for background task handler:

__block UIBackgroundTaskIdentifier backgroundTaskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{

    NSLog(@"Background Time:%f",[[UIApplication sharedApplication] backgroundTimeRemaining]);

    [[UIApplication sharedApplication] endBackgroundTask:backgroundTaskIdentifier];

    backgroundTaskIdentifier = UIBackgroundTaskInvalid;
}];
lipka
  • 1,162
  • 11
  • 19
Vardhan
  • 965
  • 2
  • 11
  • 21
10

Here's how I used beginBackgroundTaskWithExpirationHandler, including the call to the method to be done in the background. This includes code for starting the new async task. So not exactly what is asked in the question. Adding code below, thinking someone might be looking for this scenario:

- (void)didReceiveObjList:(CFDataRef)message
{
  // Received Object List. Processing the list can take a few seconds.
  // So doing it in separate thread with expiration handler to ensure it gets some time to do     the task even if the app goes to background.

  UIApplication *application = [UIApplication sharedApplication];
  __block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
    [application endBackgroundTask:bgTask];
    bgTask = UIBackgroundTaskInvalid;
}];

  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
      [self processObjList:message];

    [application endBackgroundTask:bgTask];
    bgTask = UIBackgroundTaskInvalid;
  });

  NSLog(@"Main Thread proceeding...");

}
Joe M
  • 669
  • 6
  • 9
6

Take a look at this page, Apple describe how to keep iOS application not suspended while it is in background. Apple Documentation

And here is what I've implemented:

UIBackgroundTaskIdentifier bgTask;

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 
    // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
    NSLog(@"=== DID ENTER BACKGROUND ===");
    bgTask = [[UIApplication  sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        NSLog(@"End of tolerate time. Application should be suspended now if we do not ask more 'tolerance'");
        // [self askToRunMoreBackgroundTask]; This code seems to be unnecessary. I'll verify it.
    }];

    if (bgTask == UIBackgroundTaskInvalid) {
        NSLog(@"This application does not support background mode");
    } else {
        //if application supports background mode, we'll see this log.
        NSLog(@"Application will continue to run in background");  
    }
}

/*

- (void)askToRunMoreBackgroundTask
{
    [[UIApplication sharedApplication] endBackgroundTask:bgTask];
    NSLog(@"restart background long-running task");
    bgTask = [[UIApplication  sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        NSLog(@"End of tolerate time. Application should be suspended now if we do not ask more 'tolerance'");
        [self askToRunMoreBackgroundTask]; 
    }];

}

*/

- (void)applicationWillEnterForeground:(UIApplication *)application
{
    NSLog(@"=== GOING BACK FROM IDLE MODE ===");
    //it’s important to stop background task when we do not need it anymore
    [[UIApplication sharedApplication] endBackgroundTask:UIBackgroundTaskInvalid];  
}
Duyen-Hoa
  • 15,384
  • 5
  • 35
  • 44
4

Here's a slight variation on the other answers which I'm using when the app is going to the background and I need to do something on the main thread:

- (void)applicationWillResignActive:(UIApplication *)application
{
     UIApplication *app = [UIApplication sharedApplication];

    // Register auto expiring background task
    __block UIBackgroundTaskIdentifier bgTaskId =
        [app beginBackgroundTaskWithExpirationHandler:^{
        [app endBackgroundTask:bgTaskId];
        bgTaskId = UIBackgroundTaskInvalid;
    }];

    // Execute background task on the main thread
    dispatch_async( dispatch_get_main_queue(), ^{
        // ------------------
        // DO SOMETHING INVOLVING THE WEBVIEW - WHICH MUST OCCUR ON THE MAIN THREAD!
        // ------------------
        [app endBackgroundTask:bgTaskId];
        bgTaskId = UIBackgroundTaskInvalid;
    });
}
BuvinJ
  • 10,221
  • 5
  • 83
  • 96
  • I know it's too late to ask, but why do you need to declare `app` local variable if the `application` is the parameter of `applicationWillResignActive`? – user28434'mstep May 21 '18 at 14:02
  • 1
    Good question! Having, written this 3 years ago, I don't recall. It's very possible that's a mistake on my part. I'm certain the code works as posted, but you may be able to shave off that line. Please confirm this for us if you get the chance. – BuvinJ May 21 '18 at 14:10
1

You should run your task after beginBackgroundTaskWithExpirationHandler or capture notification when the application change app's state to background and then cancel your current task and run again with beginBackgroundTaskWithExpirationHandler.

tikhop
  • 2,012
  • 14
  • 32