3

I register the background fetch task in didFinishLaunchingWithOptions of the appDelegate:

BGTaskScheduler.shared.register(forTaskWithIdentifier: BackgroundScheduler.shared.backgroundTaskId, using: DispatchQueue.main) { task in

    BackgroundScheduler.shared.handleAppRefresh(task: task as! BGAppRefreshTask)
}

The handleAppRefersh function schedules for another refresh task and calls the fetch method of the operation and also passes a completion handler to be run after finishing the fetch operation.

func handleAppRefresh(task: BGAppRefreshTask) {
    
    os_log("handleAppRefresh exetued.")
    
    scheduleAppRefresh()
    
    task.expirationHandler = {
        os_log("backgroundFetch expiration called")
    }
    
    operation.fetch() {
        os_log("backgroundFetch fetch called")
        task.setTaskCompleted(success: true)
    }
}

The scheduleAppRefresh submits BGAppRefreshTAskRequest for repeating the background fetch executions.

func scheduleAppRefresh() {
    
    os_log("scheduleAppRefresh exetued.")
    
    let request = BGAppRefreshTaskRequest(identifier: backgroundFetchId)
    request.earliestBeginDate = Date(timeIntervalSinceNow: AppSettings.refreshInterval)

    do {
        try BGTaskScheduler.shared.submit(request)
    } catch {
        os_log("Could not schedule app refresh:", error.localizedDescription)
    }
}

The refreshInterval is 10 * 60 which means 10 minutes.

The operation.fetch method checks the current location of the device and sends a request to a remote server. Finally it sends local notifications, updates UI and calls the following code:

task.setTaskCompleted(success: true)

Both the "Location update" and "Background fetch" capabilities checked in the Xcode and required settings added to the info.plist.

When I test the background fetch feature using the following debug command everything works perfectly and there is not any warning messages in debugger.

e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"TASK_IDENTIFIER"]

But when I leave the app in the background even for long hours, the iOS never calls the handleAppRefresh function.

Xcode: 12.2, iOS: 14.2

Project public repo: https://github.com/amirrezaeghtedari/CoronaVirousRegulations.git

I'm confused completely.

Iulian Onofrei
  • 9,188
  • 10
  • 67
  • 113
Justin
  • 1,786
  • 20
  • 21
  • 1
    scheduleAppRefreh is called in sceneDidEnterBackground. No, I do not force quit the app. I just send it to background. It is connected to WiFi and normally to power source. – Justin Nov 15 '20 at 18:04
  • Is your code available to public? I am not quite sure how to call my location update function (which has view controller as parameter for delegate) from the background refresh code in app delegate and my view controller updates UI. – cora Nov 16 '20 at 05:45
  • I place the public repo at the end of the question. In fact, I use os_log to log the background execution before requesting location and there is no log which means the background execution does not execute at all. – Justin Nov 16 '20 at 06:56
  • “I use os_log to log the background execution before requesting location and there is no log which means the background execution does not execute at all.” ... Personally, I don't rely on the console for historic info/debug events (because it is a ring-buffer and you might miss it). I use `UserNotifications` for debugging unpredictable events like this (so my device tells me when it's performing this background task) or log to my own log file in persistent storage. Unified logging is great for logging events whose timing it known, but for random system events, you might consider other options. – Rob Nov 16 '20 at 19:11
  • By the way, have you rebooted your device recently? I was having zero luck with app refresh on my 14.2 and 14.1 devices, but after rebooting them, they sprung to life... – Rob Nov 17 '20 at 21:28
  • https://developer.apple.com/documentation/backgroundtasks/starting_and_terminating_tasks_during_development I used this for debugging. – cora Nov 23 '20 at 05:08
  • I did the same @Cora. – Justin Nov 23 '20 at 12:11

2 Answers2

7

I had set the background fetch interval for 10 minutes and and after waiting around 4 hours or more the iOS ran my app in the background. After that my app is called in the background irregularly with intervals from less than one to some hours. It is obvious that there is no problem with my implementation and these execution intervals depend on iOS policies.

Justin
  • 1,786
  • 20
  • 21
  • The problem is that iOS is a garbage OS, sadly, and none of these "background tasks" (or "background fetches" in the past) can be relied on. Same with widget updates, etc. The only semi-reliable solution is to periodically send silent pushes to your device, and handle your background task that way. It can still break if user swipes to kill the app, but otherwise a lot more reliable. – Léo Natan Dec 22 '21 at 12:14
3

WWDC 2020 Background execution demystified

Allegedly, there are many factors in play that determine when you app actually runs background refresh. The OS's neural engine learns when the user launches app so that the updates can be performed just before. They first mention this in the 2019 session.

cora
  • 1,916
  • 1
  • 10
  • 18