8

Sorry I am stuck, but I am trying to start background task (XCode8, swift 3)

Example from here: https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html#//apple_ref/doc/uid/TP40007072-CH4-SW3

In AppDelegate.swift:

func applicationDidEnterBackground(_ application: UIApplication) {
    var bgTask: UIBackgroundTaskIdentifier = 0;
    bgTask = application.beginBackgroundTask(withName:"MyBackgroundTask", expirationHandler: {() -> Void in
        print("The task has started")
        application.endBackgroundTask(bgTask)
        bgTask = UIBackgroundTaskInvalid
    })
}

The app has never shown "The task has started" message. What am I doing wrong?

Zissouu
  • 924
  • 2
  • 10
  • 17
Alex
  • 751
  • 1
  • 9
  • 28
  • I think the expirationHandler is meant to execute as soon as the background task is complete. – Christoph Oct 04 '17 at 14:27
  • @Christoph Anyway the message should appear – Alex Oct 04 '17 at 14:30
  • @Christoph both of you have it wrong :| . The function signature is misleading. Think of that callback as just a parameter. It will only get executed **if** you get near your timeout and your task **isn't finished** yet. For more on that I highly recommend reading [this blog post](https://mfaani.com/posts/uiapplication-backgroundtasks-through-the-lens-of-closures/) I wrote – mfaani Feb 22 '22 at 16:20

2 Answers2

12

Your use of the background task is all wrong.

Waiting for the expiration handler to be called to call endBackgroundTask is poor practice and makes your app waste more resources than it needs. You should tell iOS immediately when your background task is complete.

So you should do something like:

func applicationDidEnterBackground(_ application: UIApplication) {
    var finished = false
    var bgTask: UIBackgroundTaskIdentifier = 0;
    bgTask = application.beginBackgroundTask(withName:"MyBackgroundTask", expirationHandler: {() -> Void in
        // Time is up.
        if bgTask != UIBackgroundTaskInvalid {
            // Do something to stop our background task or the app will be killed
            finished = true
        }
    })

    // Perform your background task here
    print("The task has started")
    while !finished {
        print("Not finished")
        // when done, set finished to true
        // If that doesn't happen in time, the expiration handler will do it for us
    }

    // Indicate that it is complete
    application.endBackgroundTask(bgTask)
    bgTask = UIBackgroundTaskInvalid
}

Also note that you should use beginBackgroundTask/endBackgroundTask around any code in any class that you want to keep running for a short period time even if the app goes into the background.

In this setup if the task isn't finished while the while loop is still working then the expirationHandler is called in parallel on a different thread. You should use the handler to stop your code and allow it to reach the application.endBackgroundTask(bgTask) line.

mfaani
  • 33,269
  • 19
  • 164
  • 293
rmaddy
  • 314,917
  • 42
  • 532
  • 579
  • @maddy - You are right too, but I didn't understand when the block of code in expirationHandler should be performed. – Alex Oct 04 '17 at 15:20
  • 1
    @rmaddy isn't your code incorrect? I mean if ever that line of printing took 20 minutes. (here it won't happen), then from the expirationHandler, you're not calling `application.endBackgroundTask(bgTask)` and that would result in a crash... – mfaani Oct 04 '17 at 15:24
  • @Honey That's what the expiration handler is for. See my comment "Do something to stop ..."? That "do something" would be to do something that stops whatever overly long running background task you have going. Obviously a `print` won't take too long. But say you have a `while` loop that goes on too long. The expiration handler would need to set a variable that is checked in the `while` loop so the `while` loop stops itself on the next iteration. That's one example at least. – rmaddy Oct 04 '17 at 15:26
  • Doesn't "do something to stop" **always** have to be `application.endBackgroundTask(bgTask)`? Or do you mean the lack of *any* processing is enough for the app to not be terminated by the OS? – mfaani Oct 04 '17 at 15:30
  • 1
    @Honey I do call `endBackgroundTask`. It is called after the `print` (or after the `while` from my previous comment). Calling `endBackgroundTask` tells iOS that your code is done. But you also actually need to stop your code first. Calling `endBackgroundTask` wouldn't stop my long running `while` loop. I need to stop the loop first. And the result of stopping the loop would be the code reaching my call to `endBackgroundTask`. – rmaddy Oct 04 '17 at 15:33
  • 1. I do see after the print. I'm talking about a task which takes too long so it won't reach there. 2. I don't understand what you mean by: "And the result of stopping the loop would be the code reaching my call to endBackgroundTask." if you break a loop, it won't magically call `endBackgroundTask`... Do you mind changing your answer to something that actually takes long, like a 5 minute download or loop. (I know you can use backgroundSession...). Currently IMHO this answer seems misleading or possibly incorrect – mfaani Oct 04 '17 at 15:40
  • 1. I see, so is it bad advice to just dump `endBackgroundTask` inside `expirationHandler` 2. From what I see here, it seems that the expriationHandler is ran asynchronously against the rest of the code, so it's best not to do `finished = true; application.endBackgroundTask(bgTask);` rather just `finished = true` and let the actual loop itself be the sole *ender* of the backgroundTask... – mfaani Oct 04 '17 at 15:52
  • 1
    @Honey `endBackgroundTask` should be called when your code is actually done. So in my example it makes no sense to call `endBackgroundTask` inside the expiration handler. You are correct that the expiration handler is async. The handler will be called, the flag will be set, the loop will stop, and `endBackgroundTask` will be called. – rmaddy Oct 04 '17 at 15:56
  • I think the main thing here is that background tasks are often used to do something asynchronously. If there was anything asynchronous in that while loop, then this solution could cause issues. – trees_are_great Dec 18 '19 at 09:19
  • The `while` loop prevented this code to work properly in my situation. – Tulleb Apr 14 '20 at 18:44
4

The expiration handler block gets called after a period of time in background (usually 5 minutes or so). This is meant to be used to write the clean up logic if in case you background task is taking a lot of time to complete.

There is nothing wrong with your code, you just need to wait in the background for the background task to expire.

Puneet Sharma
  • 9,369
  • 1
  • 27
  • 33
  • 1
    There is something wrong with the code. The code is written as if the expiration handler is called immediately. The print statement should not be in the expiration handler. And neither should the call to `application.endBackgroundTask(bgTask)`. Both should be after the call to `beginBackgroundTask`. – rmaddy Oct 04 '17 at 15:04
  • @rmaddy: The statement in the print is wrong, but it is acceptable to call the endBackgroundTask on application in this handler to prevent the system from killing the app. – Puneet Sharma Oct 04 '17 at 15:07
  • 1
    @Puneet Sharma You are right. After a 3-4 min the message has appeared – Alex Oct 04 '17 at 15:09
  • 2
    @PuneetSharma Waiting for the expiration handler to be called to call `endBackgroundTask` is poor practice and makes your app waste more resources than it needs. You should tell iOS immediately when your background task is complete. – rmaddy Oct 04 '17 at 15:15
  • @rmaddy: I agree with you. I assumed OP asked the question after trying out the Apple code. My answer just points out that the expiration handler does get called after few minutes. But ofcourse, after completion of any background task, the endBackgroundTask sould be called immediately. – Puneet Sharma Oct 04 '17 at 15:18
  • is this reduced to 30 seconds time from 3 minutes in iOS 13 ? – shaqir saiyed Mar 13 '20 at 09:52
  • @shaqirsaiyed I think so. "The system puts strict limits on the total amount of time that you can prevent suspension using background tasks. On current systems you can expect about 30 seconds.". From [dev forum](https://developer.apple.com/forums/thread/85066) – mfaani Feb 22 '22 at 16:23