0

I have an iOS application where the user can trigger a lengthy update process by tapping on a button. The update process involves making API calls that can take a few minutes to complete. Everything works fine when the user stays in the foreground, but if the user switches to the background, the app is not able to fetch all the necessary data.

I would like to implement a solution where, upon tapping the update button, the update process can continue running even if the app enters the background. I want to request additional background execution time to ensure that the update process completes successfully.

Based on my research, I've learned about the beginBackgroundTask(expirationHandler:) method provided by the UIApplication class, which allows an app to request additional background execution time. However, I'm unsure how to implement it correctly in my app.

Here's the current flow in my app:

The user taps the "Update" button, triggering the updateData() function. Inside updateData(), I make the necessary API calls to fetch the required data. If the app is in the foreground, everything works fine. However, if the app enters the background during the update process, the data fetching is incomplete. I would like to modify the updateData() function to request additional background execution time so that the update process can continue running until it completes, regardless of whether the app is in the foreground or background.

Could you provide guidance on how to implement this correctly? Any code examples or suggestions would be greatly appreciated.

PS. If I use a BGTask as I understand the BGTask only runs when user goes to the background mode. Is there any way to run the updateData() with a BGTask wether the app stays in foreground or background? The user might stay in foreground until the update finishes or they may go to the background. That's my main issue.

Thank you!

Mahdi
  • 31
  • 1
  • 5
  • 1
    https://stackoverflow.com/questions/64718571/beginbackgroundtask-expirationhandler-never-called – matt Jun 15 '23 at 11:22
  • 1
    https://stackoverflow.com/questions/43149007/using-beginbackgroundtaskwithexpirationhandler-for-upload – matt Jun 15 '23 at 11:23
  • I've read them before, I don't want to do download/upload tasks. they are REST API calls with some additional calculations which takes more time so using a `downloadTask` is not useful for me. Is it possible to request additional time multiple times from OS or use a BGTask in foreground so in case the user went to background the app continues fetching. @matt – Mahdi Jun 15 '23 at 11:37
  • 1
    No, neither of those is possible. – matt Jun 15 '23 at 11:41
  • what about sending silent push notifications every X seconds to the app when it goes to the background? does it help? @matt – Mahdi Jun 15 '23 at 11:45
  • Only if "every X seconds" is a rather large X (on the order of 10-60 minutes) and you don't mind if it's not always called even then. There is no way to run code in the background on a schedule you define, or indefinitely. You **must** design around this fact. (But silent push notifications are one of the most effective ways to run code periodically.) Ideally you should bundle all of your API calls into a single operation, use a background download task to execute it if it's long-running, and then use BGTask to do long-running processing. – Rob Napier Jun 16 '23 at 18:39
  • (regarding your PS; the answer is that you need to switch between modes. You need to be able to suspend foreground processing and schedule a background task to continue, then pick back up when you're in the foreground. It is a bit complex, but that is how you do this.) – Rob Napier Jun 16 '23 at 18:44

1 Answers1

0

First, beginBackgroundTask and BGTask are somewhat orthogonal. The beginBackgroundTask or beginBackgroundTaskWithName method might be appropriate for your use case. The latter, as I understand it, is mostly for doing updates while your app isn't running, so that's probably not what you're trying to do.

For the beginBackgroundTask approach, I would create a single background task independent of your network requests right before you issue the first outstanding network request, and then mark the background as done when the last task has completed and the data has been processed.

For example, if you put all your network tasks in a single NSURLSession, your completion handler block could end with a call like this:

[someAppSpecificObject callBackgroundTaskCompletionIfNeededForSession:session];

and that method would do something like this:

@property(nonatomic) NSMutableDictionary *<NSURLSession *, NSNumber *> *backgroundTaskIdentifiers;

- (void)callBackgroundTaskCompletionIfNeededForSession:(NSURLSession *)session {
  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    [session getAllTasksWithCompletionHandler:^(NSArray<NSURLSessionTask *> *tasks) {
      if (!tasks.count) {
        UIBackgroundTaskIdentifier identifier = [self. backgroundTaskIdentifiers[session] integerValue];
        [[UIApplication sharedApplication] endBackgroundTask:identifier];
      }
    }
  }
}

or if you have the potential for multiple requests outstanding that are not all queued up on the session at once, do something similar using the internal data structure of your choice.

However, if it is at all possible, it would be better to use an NSURLSessionDownloadTask to kick off the long-running job, and rely on the app getting woken up or launched in the background to process the data when the task completes. That approach is friendlier on the battery, with the caveat that issuing additional requests when the long-running request completes may result in rapidly diminishing performance, so if that is needed, it might be better to save the data, and complete the processing work when the app is next in the foreground.

dgatwood
  • 10,129
  • 1
  • 28
  • 49