Background Task Only Executes Once or Twice, Not at Regular Intervals.

My background task, implemented as per Apple's documentation, runs only once or twice instead of running continuously one after another. Seeking assistance to resolve this problem.

What I want to achieve

I want to hit the jsonbin API continuously in regular intervals

is there anything wrong in code?

AppDelegate

import UIKit
import BackgroundTasks
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    let apiKey = "xxxxxxx"  // jsonbin x-master-key
    let timestampUserDefaultsKey = "Timestamps"
    static let backgroundAppRefreshTaskSchedulerIdentifier = "com.process"
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Register a background task
        if #available(iOS 13.0, *) {
            let status = BGTaskScheduler.shared.register(
                forTaskWithIdentifier: AppDelegate.backgroundAppRefreshTaskSchedulerIdentifier, using: .global()) { task in
                    self.handleBackgroundTask(task: task as! BGProcessingTask)
                }
            print(status)
        }
        return true
    }
    
    func handleBackgroundTask(task: BGProcessingTask) {
        self.scheduleAppRefresh()
        task.expirationHandler = {
            task.setTaskCompleted(success: false)
            self.scheduleAppRefresh()
        }
        let backgroundQueue = DispatchQueue.global(qos: .background)
        
        backgroundQueue.async {
            self.postToJsonBin(prefix:"P-Background") { result in
                switch result {
                case .success(let responseJSON):
                    // Handle the success and responseJSON
                    print("Response JSON: \(responseJSON)")
                    task.setTaskCompleted(success: true)
                    
                case .failure(let error):
                    task.setTaskCompleted(success: false)
                    // Handle the error
                    print("Error: \(error.localizedDescription)")
                }
                
            }
        }
    }
    
    func scheduleAppRefresh() {
        if #available(iOS 13.0, *) {
            let request = BGProcessingTaskRequest(identifier: AppDelegate.backgroundAppRefreshTaskSchedulerIdentifier)
            // Fetch no earlier than 15 seconds from now.
            request.earliestBeginDate = Date(timeIntervalSinceNow: 60 * 5)  // 60 seconds
            //            request.requiresNetworkConnectivity = true
            do {
                try BGTaskScheduler.shared.submit(request)
                print("bg App Refresh requested")
            } catch {
                print("Could not schedule app refresh: \(error)")
            }
        }
    }
    
    
    func postToJsonBin(prefix:String, completion: @escaping (Result<Any, Error>) -> Void) {
        // Define the URL for jsonbin.io with the collection ID
        
        let apiUrl = URL(string: "https://api.jsonbin.io/v3/b")!
        
        // Define your JSON data including the timestamp parameter
        let jsonData: [String: Any] = ["timestamp": Date().currentTimestampInIST(), "prefix":prefix]
        
        do {
            // Serialize the JSON data
            let requestData = try JSONSerialization.data(withJSONObject: jsonData)
            
            // Create the URLRequest
            var request = URLRequest(url: apiUrl)
            request.httpMethod = "POST"
            request.setValue("application/json", forHTTPHeaderField: "Content-Type")
            request.setValue(apiKey, forHTTPHeaderField: "X-Master-Key") // Replace with your API key
            request.setValue(prefix, forHTTPHeaderField: "X-Bin-Name")
            
            // Set the HTTP body with the serialized JSON data
            request.httpBody = requestData
            
            // Create a URLSession task
            let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
                if let error = error {
                    completion(.failure(error))
                    return
                }
                
                if let httpResponse = response as? HTTPURLResponse,
                   httpResponse.statusCode == 200 {
                    if let responseData = data {
                        do {
                            // Parse the response data and call the completion handler
                            let responseJSON = try JSONSerialization.jsonObject(with: responseData, options: [])
                            completion(.success(responseJSON))
                        } catch {
                            completion(.failure(error))
                        }
                    }
                } else {
                    completion(.failure(NSError(domain: "JsonBinErrorDomain", code: 0, userInfo: nil))) // Replace with appropriate error handling
                }
            }
            
            // Start the URLSession task
            task.resume()
        } catch {
            completion(.failure(error))
        }
    }
}

extension Date {
    func currentTimestampInIST() -> String {
        let dateFormatter = DateFormatter()
        dateFormatter.timeZone = TimeZone(identifier: "Asia/Kolkata") // Set the time zone to IST
        
        // Define your desired date format
        dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
        
        // Get the current date and time
        let currentDate = Date()
        
        // Format the date as a string in IST
        let istTimestamp = dateFormatter.string(from: currentDate)
        
        return istTimestamp
    }
}

SceneDelegate

func sceneDidEnterBackground(_ scene: UIScene) {
    if #available(iOS 13.0, *) {
        let request = BGProcessingTaskRequest(identifier: AppDelegate.backgroundAppRefreshTaskSchedulerIdentifier)
        request.earliestBeginDate = Date(timeIntervalSinceNow: 60 * 5 )
        do {
            try BGTaskScheduler.shared.submit(request)
            print("bg App Refresh requested")
        } catch {
            print("Could not schedule app refresh: \(error)")
        }
    }
}

Background tasks don't behave as you want it to, there's no such thing as deterministic background scheduling in iOS, it simply just does not exist.

The time you specify to run is not an absolute time, it is an earliest possible time, totally not the same thing.

The documentation says "The delay between the time you schedule a background task and when the system launches your app to run the task can be many hours."

That does not go far enough in explanation, not only can it be many hours later than you specified, the OS may not run the task at all, there are no guarentees it will get run. If the user isn't regularly engaging with your app, then the OS will stop running the tasks.

This question comes up time and time and time again, people want to know how to regularly perform a task at repeating deterministic internals in the background, well you cannot, iOS just does not provide the ability to do that.

What mungbeans said plus…

The Background Tasks Resources pinned post has a bunch of links to relevant info here, including iOS Background Execution Limits.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Background Task Only Executes Once or Twice, Not at Regular Intervals.
 
 
Q