0

An app has some periodic light background work, which is expected to run even when app is suspended. Ofc, if user has swiped up, nothing can be done. When swiped from app switcher, if app was in Suspended state, it is not informed, else, in every other state (including background), applicationWillTerminate(_:) is invoked

When application is not killed by user and is in suspended state, I want to run some small tasks at certain intervals. According to this documentation, Background fetch is the way to go. This post by Apple DTS also suggests the same i.e., Background Fetch (BGAppRefreshTask) for periodic background execution.

Example: The sample background task is to count 60s in multiple of 10s. Launch handler is registered with the background task ID configured in info.plist file in application(_:didFinishLauchingWithOptions:). I have the following code invoked from in applicationDidEnterBackground(_:),

func initiateBackgroundTask(id: String) {

    let currentDate: Date = Date.now
    guard let futureDate: Date = Calendar.current.date(byAdding: .minute, value: 1, to: currentDate) else {
        NSLog(AppDelegate.TAG + "Error in obtaining future date!")
        return
    }

    let taskRequest: BGAppRefreshTaskRequest = BGAppRefreshTaskRequest(identifier: id)

    // Schedule background task
    taskRequest.earliestBeginDate = futureDate

    do {
        // Note: Background processing doesn't work in Simulator.
        // Submitting in Simulator leads to BGTaskScheduler.Error.Code.unavailable error.

        try BGTaskScheduler.shared.submit(taskRequest)
        NSLog(AppDelegate.TAG + "Task submitted successfully!")
    } catch {
        NSLog(AppDelegate.TAG + "error = " + String(describing: error))
        return
    }

    NSLog(AppDelegate.TAG + "Background task scheduled successfully!!")
    NSLog(AppDelegate.TAG + "Scheduled date = %@ | Present date = %@", String(describing: taskRequest.earliestBeginDate), String(describing: Date.now))

}

The following method is invoked from launch handler:

func taskRefreshCount1Min(task: BGAppRefreshTask) {
     
    // Set expiration handler
    task.expirationHandler = {
       NSLog(AppDelegate.TAG + "Background execution time about to expire!")
       NSLog(AppDelegate.TAG + "Remaining time = %fsec | %@", AppDelegate.app.backgroundTimeRemaining, String(describing: Date.now))

        NSLog(AppDelegate.TAG + "Unable to complete task!!")
        task.setTaskCompleted(success: false)
    }

    for i in 1...6 {
        let presentDate: Date = Date.now
        NSLog(AppDelegate.TAG + "%d) %@", i, String(describing: presentDate))
        Thread.sleep(forTimeInterval: 10)
    }

    NSLog(AppDelegate.TAG + "Task complete!")
    task.setTaskCompleted(success: true)
}

My expectation is that iOS will execute the launch handler 1 min after scheduling the background task. But that never happens, even after hours. Task did get executed within 20 mins (Sample output shown below), but every other time, I waited for more than an hour and task was never executed.

// Output
...
Task submitted successfully!
Background task scheduled successfully!!
Scheduled date = Optional(2023-02-06 09:19:46 +0000) | Present date = 2023-02-06 09:18:46 +0000
TaskRefreshCount1Min(task:)
1) 2023-02-06 09:35:12 +0000
2) 2023-02-06 09:35:22 +0000
3) 2023-02-06 09:35:32 +0000

The documentation does state that,

However, the system doesn’t guarantee launching the task at the specified date, but only that it won’t begin sooner.

But I didn't expect the results to differ by such a huge margin.

Apparently others have also faced this 'inconsistent scheduling' in iOS.

  1. Background fetch with BGTaskScheduler works perfectly with debug simulations but never works in practice
  2. How make iOS app running on background Every one minute in Swift?

In links I have pasted above, despite Background Fetch being the most suitable for periodic light tasks, Silent Push Notifications were suggested. I'm currently checking out Silent Push Notifications, but iOS recommends a frequency of not more than 3 per hour.

Summarising my questions from this exercise:

  1. Is something wrong with the code?
  2. What technique should I use? Background fetch, silent notifications or anything else?
  3. In Background Fetch, how do you schedule background tasks periodically? There's an instance property called earliestBeginDate to denote the minimum time after which the task can happen, but that's all.
  4. As shown in output, expiration handler is not invoked (unlike the expiration handler of beginBackgroundTask(withName:expirationHandler:))?

Environment: Xcode 14.2, iOS 16.0, Debug and Release build, Console app (to check logs).

NightFuryLxD
  • 847
  • 5
  • 15
  • 1
    You've correctly linked to the duplicate of this. This is not possible in iOS. See the comments in the linked question. There is no such thing as a periodic background task. There are only background tasks that run when the OS chooses to run them. Push notifications can achieve some periodic behavior with limitations, but generally they should only be sent when things change, not on a specific periodic schedule. Generally you should redesign to not require a periodic task (most tasks designed with periodic timers do not actually require that; you'd need to describe what you're doing). – Rob Napier Feb 06 '23 at 14:12
  • 1
    Note that the documentation's claim that it doesn't "guarantee launching the task at the specified date" should be taken to the fullest extreme. It does not guarantee launching **at all**. It is merely a request you can make to the system for something unnecessary that may help user experience. – Rob Napier Feb 06 '23 at 14:14
  • @RobNapier I thought it should be possible, because the first two links suggested Background fetch for scheduling background tasks periodically. But since the actual class representing Background fetch i.e BGAppRefreshTask, didn't have any way to schedule periodic tasks, thought I missed something. Anyway, in the end, iOS may or may not honour and if it honours, it may or may not happen at the requested time (as mentioned in documentation). Thanks. – NightFuryLxD Feb 07 '23 at 03:31

0 Answers0