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

391 views Asked by At

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.

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)")
        }
    }
}

Observation

Gave 5 minutes as the earliest begin time interval and kept in observation for 18 hours and observed that it got executed only twice or sometimes only once.

what i want

  1. I want background tasks to get executed continuously even if execution time is more than expected.
  2. is there anything wrong with the code?
  3. if this is normal behavior then how can I achieve point 1.
1

There are 1 answers

0
benc On

If you want the background task to run every 5 minutes, you could do this:

Wake Your App with a Background Push

This does require a non-trivial amount of real work getting APNS working (and keep in mind, a silent-background notification is not a local notification).

That being said, I think don't think your approach is ideal for the application feature you've described, I think the comments explain this well. Visual latency for loading app data over the wire is a common problem, there are many ways to solve it beside having a local-background task do a polling / data-pull.