Let's say an app has a background task to execute after 1 hour, but when it executes, it discovers that the user has no internet connection, so it cannot do its job. Is it possible to schedule another background task inside the background task to execute after another hour?
1 Answers
Yes, you can schedule the next task when processing the current task.
The code example in Using Background Tasks to Update Your App does precisely that, scheduling the next task (scheduleAppRefresh
) as the first step in handling an app refresh:
func handleAppRefresh(task: BGAppRefreshTask) {
// Schedule a new refresh task.
scheduleAppRefresh()
// Create an operation that performs the main part of the background task.
let operation = RefreshAppContentsOperation()
// Provide the background task with an expiration handler that cancels the operation.
task.expirationHandler = {
operation.cancel()
}
// Inform the system that the background task is complete
// when the operation completes.
operation.completionBlock = {
task.setTaskCompleted(success: !operation.isCancelled)
}
// Start the operation.
operationQueue.addOperation(operation)
}
Also see Refreshing and Maintaining Your App Using Background Tasks sample project.
For what it is worth, the above custom Operation
subclass example (the RefreshAppContentsOperation
object) might be a source of confusion for contemporary readers. The challenge is that when Apple wrote that example, a custom, asynchronous, Operation
subclass was the state of the art for nice, encapsulated, cancelable, asynchronous units of work. But there now are better, more modern alternatives. E.g., we might now use the async
-await
of Swift concurrency:
func handleAppRefresh(task: BGAppRefreshTask) {
// Schedule a new refresh task.
scheduleAppRefresh()
// Start refresh of the app data
let updateTask = Task {
do {
try await self.refreshApp()
task.setTaskCompleted(success: true)
} catch {
task.setTaskCompleted(success: false)
}
}
// Provide the background task with an expiration handler that cancels the operation.
task.expirationHandler = {
updateTask.cancel()
}
}
func refreshApp() async throws {
// update the app with Swift concurrency, e.g.,
let (data, response) = try await URLSession.shared.data(from: url)
…
// update model and presumably save it in local, persistent storage
}
But our apps may use wildly different mechanisms and API to fetch data updates, so it is best not to get lost in the details here. The key observations are that when we handle an app refresh, we:
- schedule the next refresh;
- initiate the asynchronous refresh of our app’s data;
- when our request finishes, mark the
BGAppRefreshTask
as complete; and - if our request cannot finish in time, let the
expirationHandler
of ourBGAppRefreshTask
cancel our app refresh request.

- 415,655
- 72
- 787
- 1,044
-
The documentation looks useless for newbies. there is no code for RefreshAppContentsOperation. – chitgoks Feb 24 '23 at 14:05
-
That is not an Apple API, but rather just a placeholder/example. So replace that `RefreshAppContentsOperation` with you own code that you have to refresh your app. You want to make sure that you call `task.setTaskCompleted` when your app update process is done. And, if possible, you want to make sure that you handle cancelation gracefully, too. – Rob Feb 24 '23 at 14:32
-
BlockOperation would have helped easily rather than some non existent class that makea one clueless what to put in. – chitgoks Feb 25 '23 at 15:08
-
1Yeah, I hear you. It’s not a great example. The issue is that back when this was written, custom `Operation` subclass was the state of the art for encapsulated, cancelable, and asynchronous units of work. A `BlockOperation` would be much worse example, IMHO, as that is for tasks that are generally synchronous and not cancelable, both of which are antithetical to the design principles of background tasks. I’ve updated my answer with a Swift concurrency example, which would be a good contemporary approach. – Rob Feb 25 '23 at 16:00