I am able to setup and schedule a background refresh task as well as manually trigger it via Xcode and the simulator tied to my iPhone 11 Pro test phone using the e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:
However, it won't execute on the app on it's own. And yes, the pinfo.list entries are correct and match!
I know the scheduler is not exact on timing but it's just not executing on its own. Since I can trigger manually it I'm pretty sure the code is good but I must be missing something.
I created an observable object for this code and the relevant parts look like this:
class BackgroundTaskHandler: ObservableObject {
static let shared = BackgroundTaskHandler()
var taskState: BackgroundTaskState = .idle
let backgroundAppRefreshTask = "com.opexnetworks.templateapp.shell.V1.appRefreshTask"
func registerBackgroundTask() {
BGTaskScheduler.shared.register(forTaskWithIdentifier: backgroundAppRefreshTask, using: nil) { task in
self.handleAppRefresh(task: task as! BGAppRefreshTask)
}
self.updateTaskState(to: .idle, logMessage: "✅ Background app refresh task '\(backgroundAppRefreshTask)' registered.")
BGTaskScheduler.shared.register(forTaskWithIdentifier: backgroundTaskIdentifier, using: nil) { task in
self.handleProcessingTask(task: task as! BGProcessingTask)
}
self.updateTaskState(to: .idle, logMessage: "✅ Background task identifier '\(backgroundTaskIdentifier)' registered.")
}
// Handle the app refresh task
private func handleAppRefresh(task: BGAppRefreshTask) {
self.updateTaskState(to: .running, logMessage: "🔥 app refresh task is now running.")
PostNotification.sendNotification(title: "Task Running", body: "App refresh task is now running.")
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
let operation = BlockOperation {
self.doSomeShortTaskWork()
}
task.expirationHandler = {
self.updateTaskState(to: .expired, logMessage: "💀 App refresh task expired before completion.")
PostNotification.sendNotification(title: "Task Expired", body: "App refresh task expired before completion \(self.formattedDate(Date())).")
operation.cancel()
}
operation.completionBlock = {
if !operation.isCancelled {
self.taskState = .completed
}
task.setTaskCompleted(success: !operation.isCancelled)
let completionDate = Date()
UserDefaults.standard.set(completionDate, forKey: "LastBackgroundTaskCompletionDate")
self.updateTaskState(to: .completed, logMessage: "🏁 App refresh task completed at \(self.formattedDate(completionDate)).")
PostNotification.sendNotification(title: "Task Completed", body: "App refresh task completed at: \(completionDate)")
self.scheduleAppRefresh() // Schedule the next one
}
queue.addOperation(operation)
}
func scheduleAppRefresh() {
// Check for any pending task requests
BGTaskScheduler.shared.getPendingTaskRequests { taskRequests in
let refreshTaskIdentifier = self.backgroundAppRefreshTask
let refreshTaskAlreadyScheduled = taskRequests.contains { $0.identifier == refreshTaskIdentifier }
if refreshTaskAlreadyScheduled {
self.updateTaskState(to: .pending, logMessage: "⚠️ App refresh task '\(refreshTaskIdentifier)' is already pending.")
// Iterate over pending requests to get details
for taskRequest in taskRequests where taskRequest.identifier == refreshTaskIdentifier {
let earliestBeginDate: String
if let date = taskRequest.earliestBeginDate {
earliestBeginDate = self.formattedDate(date)
} else {
earliestBeginDate = "never"
}
self.updateTaskState(to: .pending, logMessage: "⚠️ Pending Task: \(taskRequest.identifier), Earliest Begin Date: \(earliestBeginDate)")
}
// Optionally, show a warning message to the user in your app
PostNotification.sendNotification(title: "Pending Tasks", body: "App refresh task is already pending. Task scheduling cancelled.")
return
} else {
// No pending app refresh task, so schedule a new one
let request = BGAppRefreshTaskRequest(identifier: refreshTaskIdentifier)
request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60) // Earliest in 15 minutes
do {
try BGTaskScheduler.shared.submit(request)
self.taskState = .scheduled
self.updateTaskState(to: .scheduled, logMessage: "✅ App refresh task '\(refreshTaskIdentifier)' successfully scheduled for about 15 minutes later.")
PostNotification.sendNotification(title: "Task Scheduled", body: "App refresh task has been scheduled to run in about 15 minutes.")
} catch {
print("Could not schedule app refresh: \(error)")
self.taskState = .failed
self.updateTaskState(to: .failed, logMessage: "❌ Failed to schedule app refresh task.")
}
}
}
}
// Short task work simulation
private func doSomeShortTaskWork() {
print("Doing some short task work...")
// Simulate a short background task (e.g., fetching new data from server)
sleep(5)
print("Short task work completed.")
}
In my AppDelegate I trigger the registerBackground task in the didFinishLaunchingWithOptions here:
BackgroundTaskHandler.shared.registerBackgroundTask()
And I scheduled it here in the launch view under a task when visible:
.task {
BackgroundTaskHandler.shared.scheduleAppRefresh()
}
I've also tried the last in the AppDelegate after registering. either way the task schedules but never executes.