Andy Hill
Andy Hill

Reputation: 158

How to tell if my iOS 13 background task has run?

I'm trying out iOS Background Task for the first time. Following the sample WWDC'19 code, I set up a simple app that sets a UserDefaults key's value to 1 in the ViewController.viewDidLoad() and set it to 2 in the background task. I have the value printed before entering background and upon returning to foreground. The value consistently prints as 1, even after letting the app sit in the background overnight. Is my background task not run? Or could it be that my UserDefaults value is not synchronized between the foreground and background tasks, as discussed in several stackoverflow questions? Or am I doing something else wrong? Any pointer appreciated.

I have checked the "Background fetch" item in "Background Modes" under "Signing & Capabilities" and I have also added "com.example.BGTask.refresh" as "item 0" under "Permitted background task scheduler" in my Info.plist. I'm running the app on device, tethered to Xcode.

iOS 13.5.1, Xcode 11.5

AppDelegate.swift:

import UIKit
import BackgroundTasks

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.example.BGTask.refresh", using: nil) { task in
            self.handleAppRefresh(task: task as! BGAppRefreshTask)
        }
        return true
    }

    func scheduleAppRefresh() {
        let request = BGAppRefreshTaskRequest(identifier: "com.example.BGTask.refresh")
        request.earliestBeginDate = Date(timeIntervalSinceNow: 15*60)
        do {
            try BGTaskScheduler.shared.submit(request)
        } catch {
            print("Could not schedule app refresh: \(error)")
        }
    }

    func handleAppRefresh(task: BGAppRefreshTask) {
        scheduleAppRefresh()

        task.expirationHandler = {
            task.setTaskCompleted(success: false)
        }

        UserDefaults.standard.set(2, forKey: "bgtask")
        task.setTaskCompleted(success: true)
    }


    // MARK: UISceneSession Lifecycle
    // rest of templated AppDelegate.swift

SceneDelegate.swift:
Following this tutorial, I call scheduleAppRefresh() in SceneDelegate instead of AppDelegate and have modified these two functions:

    func sceneDidBecomeActive(_ scene: UIScene) {
        print("didBecomeActive: ", UserDefaults.standard.integer(forKey: "bgtask"))
    }

    func sceneDidEnterBackground(_ scene: UIScene) {
        print("didEnterBackground: ", UserDefaults.standard.integer(forKey: "bgtask"))
        (UIApplication.shared.delegate as! AppDelegate).scheduleAppRefresh()
    }

Finally, in ViewController.swift:

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        UserDefaults.standard.set(1, forKey: "bgtask")
        print("viewDidLoad: ", UserDefaults.standard.integer(forKey: "bgtask"))
    }
}

This is what I get printed out on the Xcode console:

viewDidLoad:  1
didBecomeActive:  1
                        // tap the Home button
didEnterBackground:  1
                        // after a suitably long time, tap the app icon
didBecomeActive:  1

Why doesn't UserDefaults.shared.integer(forKey: "bgtask") get incremented by the second didBecomeActive?

Upvotes: 7

Views: 7581

Answers (2)

Andy Hill
Andy Hill

Reputation: 158

Well, I got background processing to work. Xcode console output:

viewDidLoad:  1
didBecomeActive:  1
didEnterBackground:  1
didBecomeActive:  1
didEnterBackground:  1
                           // after about 15 minutes
didBecomeActive:  2
didEnterBackground:  2
                           // after staying in background overnight (~8 hours)
didBecomeActive:  19
didEnterBackground:  19

Instead of "Background fetch," I ticked "Background processing" in "Background Modes" under "Signing & Capabilities".

AppDelegate.swift:

import UIKit
import BackgroundTasks

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.example.BGTask.refresh", using: nil) { task in
            self.handleAppRefresh(task: task as! BGProcessingTask)
        }
        return true
    }

    func scheduleAppRefresh() {

        let request = BGProcessingTaskRequest(identifier: "com.example.BGTask.refresh")
        request.requiresNetworkConnectivity = false
        request.requiresExternalPower = false

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

    func handleAppRefresh(task: BGProcessingTask) {
        scheduleAppRefresh()
        
        task.expirationHandler = {
            task.setTaskCompleted(success: false)
        }
        
        // increment instead of a fixed number
        UserDefaults.standard.set(UserDefaults.standard.integer(forKey: "bgtask")+1, forKey: "bgtask")

        task.setTaskCompleted(success: true)
    }
    
    // MARK: UISceneSession Lifecycle
    // rest of templated AppDelegate.swift

And no other changes to the other files.

Now why does BGProcessing work while BGAppRefresh doesn't?

Upvotes: 2

Wahyudimas
Wahyudimas

Reputation: 217

after I watch wwdc2019

https://developer.apple.com/videos/play/wwdc2019/707/

The task will be executed only when the system decides to do so

You can check to this forum discussion link too :

https://forums.developer.apple.com/thread/124320

Upvotes: 2

Related Questions