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.
There's no point setting the minimum time to 15 minutes and then sitting watching it and assuming it'll trigger after 15 minutes, or not long after that. It might not be until a few or several hours later, probably during the night in my experience , have you tried waiting that long?