6 Replies
      Latest reply: Dec 26, 2016 4:59 PM by WoodEnt RSS
      WoodEnt Level 1 Level 1 (0 points)

        When my app is about to be terminated, I receive a notification (UIApplicationWillTerminateNotification) so I can fire off a URLSession to have some data sent to a remote server. I'm trying to do this using UIBackgroundTask, which according the Apple documentation gives me 5 seconds to perform a URLSesstionDataTask. The problem, however, is that my code runs completely fine, except for the fact that the completionHandler of my data task doesn't get executed. It has nothing to do with the 5 seconds expiring, because I tried executing some code AFTER the data task and that code runs fine as well. I also used DispatchQueue to run the completionHandler in the main thread and it didn't work either. Does anyone know first of all if this is possible, and if so, how to do it?

        • Re: Execute completionHandler in UIBackgroundTask
          eskimo Apple Staff Apple Staff (6,685 points)

          Last I checked UIApplicationWillTerminateNotification only kicks in if the user terminates your app from the multitasking API while your app is running.  In the more common case, where the user terminates your app while the app is suspended in the background, you never receive UIApplicationWillTerminateNotification.  Given that, I’m not sure whether it’s worthwhile you handling this case at all.

          Regardless, I think the problem you’re having is that, after you return from your UIApplicationWillTerminateNotification observer, the process terminates, and takes your networking request with it.  To avoid this, you’ll have to block inside your observer.  This is tricky for two reasons:

          • If the app is in the foreground, you’ll lock up its UI.

          • If you block too long, the watchdog will summarily kill your process.

          Share and Enjoy

          Quinn “The Eskimo!”
          Apple Developer Relations, Developer Technical Support, Core OS/Hardware
          let myEmail = "eskimo" + "1" + "@apple.com"

            • Re: Execute completionHandler in UIBackgroundTask
              WoodEnt Level 1 Level 1 (0 points)

              So then what would you suggest is the 'safest' way to catch the app terminating event? It's very important the data task is performed both when my app is terminated from the foreground AND from the background/suspended state. And also, how do I "block inside my observer"? Right now I'm just having my observer perform a selector method as soon as the notification is received. In my didFinishLaunchingWithOptions delegate method of the AppDelegate I have the notification set up like this:

              NotificationCenter.default.addObserver(self, selector:#selector(AppDelegate.onAppWillTerminate(notification:)), name:NSNotification.Name.UIApplicationWillTerminate, object:nil)
              

              And as soon as the notification is received, onAppWillTerminate(notification:) gets called:

              func onAppWillTerminate(notification:Notification) {
                      // Data task is executed right here
                  }
              

              I've tested running big chunks of code in this method and it never got interrupted, so I assume I never ran out of time. It seems very unlikely I would run out of time, since the data task takes very little time to be executed. Basically, what I'm saying is I don't think the process gets terminated before it's supposed to: it finishes the entire process all the way down to the last line of code. Most likely, the REAL problem is that it 'conciously' skips completion handlers and other processes that are normally supposed to be executed in a background thread. This is an issue I ran into before when I was trying to perform a data task in my AppDelegate's applicationDidEnterBackground method, but that issue got solved using the UIBackgroundTaskIdentifier class, which doesn't seem to be doing anything in the method posted above.

               

              Anyway, I'd very much appreciate it if you could first of all tell me if there's a safer alternative to using this UIApplicationWillTerminateNotification in order to ALWAYS catch the app terminating event. I know there's tons of iOS apps out there that require some sort of server connection when the app gets terminated (for example, WhatsApp updates your "last seen" status whenever you either enter the background state or terminate your app altogether) so it seems very unlikely to me it's IMPOSSIBLE to do this. I hope you can point me in the right direction on this. Thanks in advance!

                • Re: Execute completionHandler in UIBackgroundTask
                  eskimo Apple Staff Apple Staff (6,685 points)

                  Note: I moved this thread over to App Frameworks > iOS Multitasking because there’s nothing Swift specific here.

                  Also, I’m happy to go into the details of how to wait in the UIApplicationWillTerminateNotification but first let’s deal with the core problem, namely:

                  It's very important the data task is performed both when my app is terminated from the foreground AND from the background/suspended state.

                  The latter is simply not possible.  iOS will never resume a suspended app solely to inform it that it’s about to be terminated.  Doing that would run counter to the system’s overall memory management design.  Remember that iOS tries to keep an app suspended and in memory as long as possible.  It only dumps the out of memory when the system is short of memory.  Resuming the app to deliver the UIApplicationWillTerminateNotification event would increase memory usage at a time when the system is already short of memory.

                  As a rule, the time to do this sort of clean up is when your app becomes eligible for suspension, which typically means on the UIApplicationDidEnterBackgroundNotification event.

                  Share and Enjoy

                  Quinn “The Eskimo!”
                  Apple Developer Relations, Developer Technical Support, Core OS/Hardware
                  let myEmail = "eskimo" + "1" + "@apple.com"

                    • Re: Execute completionHandler in UIBackgroundTask
                      WoodEnt Level 1 Level 1 (0 points)

                      That's okay. I implemented the same data task in both applicationDidEnterBackground() and the UIApplicationWillTerminateNotification method, so even if the app gets terminated from the background/suspended state, the required data task will already have been performed the moment my app was moved to the background. I'm pretty sure I have a catch set up for all scenario's right now, so the only problem there's left is that the completionHandler of the actual data task doesn't get executed on app termination. I've searched everywhere and I even found a topic on an external forum from someone who was facing the exact same issue as I am right now, but no one was able to answer his question, so I'm starting to get the suspicion that this is simply not possible, which leaves me wondering how the earlier mentioned apps handle their data tasks on app termination. There HAS to be one way or another to do this, right?

                        • Re: Execute completionHandler in UIBackgroundTask
                          eskimo Apple Staff Apple Staff (6,685 points)

                          I implemented the same data task in both -applicationDidEnterBackground … so even if the app gets terminated from the background/suspended state, the required data task will already have been performed the moment my app was moved to the background.

                          Cool.  Most folks who ask me about this want to stay logged in while suspended in the background, which makes things much harder.

                          So the only problem there's left is that the completionHandler of the actual data task doesn't get executed on app termination.

                          OK.  To fix this you have to block the main thread within -applicationWillTerminate: method (or, equivalently, in your UIApplicationWillTerminateNotification observer) waiting for the network request to complete.  The standard approach for this is to use a dispatch semaphore.  For example:

                          let sem = DispatchSemaphore(value: 0)
                          startSomethingAsync(completionHandler: {
                              sem.signal()
                          })
                          sem.wait()
                          

                          IMPORTANT There’s two key things to note here:

                          • You have to make sure that the async work doesn’t rely on any work to be done on the main thread (or queue).  If it does, you’ll deadlock, because the main thread is blocked on line 5.

                          • In real code you should wait with a timeout so that you don’t block for too long if the network is not working.  Without the timeout, you could block indefinitely, and you’ll eventually be killed by the watchdog (which will generate a crash log, which you don’t need).


                          Finally, be aware that this doesn’t guarantee that you’ll successfully log out of your server when quitting.  For example:

                          • The network could be offline when you terminate (or move to the background)

                          • You might crash, at which point none of this code will run

                          You’ll have to engineer your server to be resilient in the face of such failures.

                          Share and Enjoy

                          Quinn “The Eskimo!”
                          Apple Developer Relations, Developer Technical Support, Core OS/Hardware
                          let myEmail = "eskimo" + "1" + "@apple.com"

                            • Re: Execute completionHandler in UIBackgroundTask
                              WoodEnt Level 1 Level 1 (0 points)

                              Yes! It works! Thank you so much! I honestly did not expect the solution to be something as simple as this. I've been digging for the answer for a really long time and since no one was able to help me out, I'd lost all hope that this was even possible to do. Thank you for the clear explanation, and as for the heads-ups you gave me, I've already taken care of most scenario's of app termination and suspension, including scenario's in which the device is not connected to the internet when moving to the background. The only problem I was left dealing with was the problem described above, but thanks to your help I got that covered too, so now I'm all done!