Streaming is available in most browsers,
and in the Developer app.
-
Finish tasks in the background
Discover background execution advancements and understand how the system schedules runtime. We'll discuss how to get the most out of background runtime to allow your app to deliver features in the background while maintaining a great foreground experience. We'll also cover how APIs provide background runtime for your app, and how each API is tailored for different use cases — including new APIs in iOS and iPadOS 26 that let your app finish tasks as your app transitions from the foreground to the background.
Chapters
- 0:00 - Welcome
- 1:22 - Introduction
- 2:09 - Behaviors and constraints
- 7:29 - Background task APIs
- 11:23 - Continued processing tasks
Resources
-
Search this video…
Hey, I’m Ryan, a software engineer here at Apple. Today, I’d like to chat about how your app can embrace background runtime.
The App Store is home to millions of apps, and each one brings something unique to the table. Your app shines brightest when it's front and center. With every pixel at your disposal, you’re able to foster memorable experiences.
And people expect this - fast, useful, well-crafted apps from developers like yourself.
But what happens when the app transitions to the background? In this session, I’ll explore background runtime, building a toolkit that fits your app, one that’s efficient and well-behaved. It's not just about staying alive. It’s about being useful, doing things like prefetching, syncing, and uploading, setting your app up for success the next time it launches. Used well, background runtime makes your app feel fast, seamless, and maybe even a little magical.
I’ll start with a quick overview of what foreground and background actually mean and share some principles about how the system decides when and how frequently apps run in the background.
Then I’ll cover many of the existing means to do so, including a new API for continuing tasks that were started in the foreground.
Every foregrounded app follows the same rhythm.
The app itself and everything it needs frameworks, assets, and more are loaded into memory. It’s in this state where your app’s interface is the focus on the device, and your app is defined as foregrounded.
When someone leaves your app, but the process stays alive, it enters the background. By default, backgrounded apps are suspended. They don't get CPU time. This protects battery life, preserves privacy, and frees up resources for apps in the foreground.
In some cases, your app can request background time to finish up in-flight work before being suspended. When someone returns to your app through the app switcher, it’s resumed by the system as it moves to the foreground.
Before using background runtime, it’ll help to understand how the system prioritizes and manages resources, and what you can do in your app to build the best experience.
The system’s main goals are straightforward: protect battery life and optimize performance, while preserving a fluid and responsive experience.
That means background execution isn’t guaranteed. Instead, it’s opportunistic, often discretionary, and tightly managed. Successful workloads understand this context and are designed to work with the system, not against it.
The most fundamental constraint is energy.
Every operation, CPU cycles, GPU rendering, network requests, even Neural Engine usage has an associated battery cost. Battery life is a finite resource. And to preserve it, the system coalesces work when the devices wake, cutting down on unnecessary background activity throughout the day. Since background runtime is limited, think about the things your app should do in the background as discrete, tailored tasks. Each task does one thing efficiently, maintaining awareness of the system's priorities and constraints. This background work is reflected in Battery Settings, where people learn which apps have significant impact on their battery life. iOS 26 brings incredible insights to your device's battery performance, with detailed app-specific breakdowns.
Your best move here is to be efficient. If a task doesn't need to run immediately, consider deferring it until the device is charging. And if it does need to run, keep it lightweight and purpose-driven.
In addition to battery, the system is also responsible for managing other shared resources things like memory, CPU time, and network bandwidth. When someone is using their device, the foreground app is prioritized. If a backgrounded app consumes too much memory or CPU, it's not just inefficient. It’s also competing with the foreground experience. This is when the system may step in. Throttling, suspending, or even terminating processes that prove too costly.
The takeaway is simple. Keep your background work minimal. Avoid bloated work and prefer batch processing to minimize your memory footprint.
Even when workloads are well-behaved, there’s no guarantee they’ll be allowed to run for long. The queue of background work is never empty, so the system may opt to prioritize other workloads. That said, we’re all working as a team here, with the system doing its job of maximizing the foreground experience.
Your workloads must be resilient. Save incremental progress early and often. Respond to expiration signals promptly and trust the system will soon return to your workload. It appreciates cooperative processes, using these behaviors to influence future scheduling. But at the end of the day, the person using the device has the last say. They influence scheduling by toggling settings like Low Power Mode, Background App Refresh, and Low Data Mode. The system provides transparency, empowering folks to make these decisions on their own. For example, if your app drains too much battery in the background, they may take action, and not necessarily in your favor.
Therefore, background work should be courteous, stay lightweight and honor preferences while making sure your impact is proportional to the value you’re delivering.
Now even when every process in the system fully embraces these principles, they’re still running in a complex and highly dynamic environment.
Things like network availability, CPU load, device activity, thermal state, and battery level are all used as context to inform scheduling decisions.
Fortunately, the system accounts for these challenging conditions, forging the best experience at all times. This means that even well designed tasks may be postponed if the conditions aren’t quite right, optimizing for the bigger picture. It's crucial to maintain adaptability. Keep your work atomic and lightweight while clearly advertising your requirements. Designing workloads to pick up where they left off allows for incremental progress as runtime opportunities arise. The more you understand and adapt to system conditions and priorities, the more successful your workloads will be.
Efficient, Minimal, Resilient, Courteous, and Adaptive. These are the keys to building background work that fits seamlessly into the platform. And it may help to ask yourself a few key questions during development.
Who initiated the task? Was the work explicitly kicked off or is it something discretionary that could run at a later time? How long will the work take? Try to categorize your tasks into short, medium, and long durations. Is this work critical to app state and freshness? Background downloads enhance the feeling of liveliness to your app, but telemetry uploads have no immediate benefit to the device owner. And finally, does the work require consent or input? Background runtime isn’t appropriate for these types of workloads, so I’d recommend taking a different approach if that’s the case.
With this foundation in place, let’s explore how you can effectively design your tasks. iOS provides a number of different APIs that let you request background runtime, and each API assumes a distinct type or profile depending on the support of work. This enables the system to adjust your app’s runtime to the constraints and conditions I talked about earlier. For example, folks expect their most frequently used apps to always have the latest and greatest content. It makes sense for the system to understand app usage patterns, optimizing background tasks and support.
This is the case for the first API, BGAppRefreshTask.
Apps can use this to silently fetch content from servers moments before use while fully embracing the idea of courtesy. The system aligns these tasks with app usage history.
Frequently used apps have an increased chance of being scheduled, guaranteeing fresh content on each launch.
To create an app refresh task in SwiftUI, add the BackgroundTask modifier to a scene. When the system wakes the app in the background, it will invoke this closure, suspending the app when the closure returns.
While refresh tasks cover fetch based use cases, you may also want to maintain remote documents that are updated infrequently or irregularly.
Background Push Notifications provide an elegant solution. When your server sends a notification about new content, the system will wake your app at an opportune time to fetch it. Note this is different from the app refresh case. Here an update is pushed to the device rather than it opportunistically fetching data.
Because Background Push Notifications are used to indicate new remote content, they are always considered discretionary.
They’re also sent at a low priority and coalesced to minimize overhead and power cost.
And when someone removes your app from the app switcher, the system respects their intent. The notifications are not delivered to your app until it’s launched again.
Sometimes, though, you may want your app to perform other types of work. Maybe you’d like to run an ML model on generated data, or even simply handle database maintenance.
The BGProcessingTask API enables you to do just that.
Registering for this task is straightforward. All it takes is a task identifier, callback queue, and a closure that's invoked at runtime.
You must register BackgroundTasks immediately during launch. This way, the system is promptly made aware of your task if it's launched in the background, invoking its handler immediately. Processing tasks also support additional configurations in pursuit of the principles I covered earlier. For example, if the work isn’t particularly sensitive to latency, a great task may elect to only run when the device is on charger and connected to network. This minimizes battery impact, reducing your app’s footprint and battery settings.
So far, I’ve covered APIs that let your app get runtime for tasks that begin in the background. But sometimes you may just want a little bit more time to continue running while transitioning to the background.
The begin and end background task APIs allow your app to finish work that may be irrecoverable should it be interrupted and left incomplete.
Take even a simple state save, for example. Depending on the work, premature termination may lead to a poor experience.
Wrapping your code in these API calls informs the system that your app is handling crucial work that should not be interrupted. This API is a great fit when you’re cleaning up file handles or closing database connections.
The landscape of background support on iOS is vast, designed to handle a wide variety of tasks, including those kicked off by the user.
Ensuring these user-initiated operations complete reliably is essential for a great experience. In an iPadOS and iOS 26, the BG continued processing task provides your app a way to support these exact features. This task allows you to continue work even after the app is backgrounded, with a system providing UI to communicate progress. Take the Journal app, for example. It uses the continued processing task to drive exports in the background with progress updates reflected to the initiator.
And once it completes, the system briefly updates, then automatically dismisses the UI.
People remain in control. They can consult the progress in the task and cancel the work at any time, enabling complex features.
Continue processing tasks always start with an explicit action that someone performs in your app, like a button tap or gesture. Each task represents a clear and immediate goal that someone wants your app to perform, like exporting file, publishing social media content, or completing an update on a connected accessory. These tasks make measurable progress, and it's easy to understand what it means when the task is finished.
People don't expect tasks to start automatically, even if they’ve set a preference in your app before. Avoid automatic workloads like maintenance, backups, or photo syncing. If a task starts without an explicit action, people may not understand the goal of the task or what its progress really means.
Doing this unexpected work may lead to your app’s task being canceled.
For these needs, consider other APIs that are a better fit.
With these considerations in mind, adopting continued processing tasks is a breeze. To begin, add a task identifier to your Info.plist. You’ll use this to register a launch handler that manages state and progress reporting. Then, simply submit a task request when prompted.
Let's start with the identifier. You’ll define this by adding a new value to the permitted background task scheduler identifiers array in your Info.plist, ensuring it is prefixed with your app's bundle ID.
In addition to static identifiers, continued processing tasks support a new wildcard notation with a dynamic suffix.
Wildcard identifiers always begin with your bundle ID, followed by some semantic context. The new component here is the dot asterisk, indicating a dynamic suffix will be appended to the identifier at registration and submission.
A fully composed identifier, used to register and submit, takes the form like so.
For now, let’s stick with something more static.
With that selected, the scheduler needs to know what code to execute when your continued processing task is asked to run. Like before, a closure provided to the scheduler is invoked after a task request is submitted. But here's a crucial shift. Launch handlers don’t need to be registered before your app finishes launching. Instead, you’ll now dynamically register these handlers when the intent to use them is expressed.
Just like in Journal, it’s imperative that you provide timely updates about workload progression. And if that progression is slower than expected, the system will prompt the initiator, asking if they want the work to continue.
The system relies on these progress updates to manage ongoing work. As a consequence, tasks that do not report any progress will be expired, allowing the system to reclaim and redistribute the resources. Your app actively communicates these updates using the Progress Reporting Protocol. As it does, the system continuously monitors them, surfacing the progress in the UI.
Now we still need to be mindful of the fact that the system may need to stop your task early if conditions change. To handle this, your task must provide an expiration handler that will be called when it's time to stop. Think of the handler as your chance to quickly flip a variable so that the task can stop gracefully and avoid doing extra work.
Critically, when your task does complete its work, you must call setTaskCompleted. This tells the system you're finished.
OK, I’ve covered how to manage progress updates, handle potential interruptions, and signal completion of a running task. Now, let’s put it together by building and submitting a valid, user-initiated task request.
You start by initializing a task request object. This needs three pieces of information an identifier matching one from your Info.plist, a localized title, and a localized subtitle. These are what the user sees in the system UI.
Next, you’ll need to provide a submission strategy for the system to abide by. By default, if the system finds it cannot immediately run your continued processing task, it will add it to the back of the queue. You don’t need to specify anything extra for this.
However, sometimes queuing isn’t quite the right approach. What if the task is only useful if it starts right now? Instead of letting it queue, you can tell the system to fail the submission if it cannot start immediately. This gives your app instant feedback, allowing you to handle the situation appropriately.
Once you’ve decided on your strategy and configured the request, you just submit it to the scheduler and let the system manage the workload. And that’s the core process when working with continued processing tasks. By providing the title and subtitle and considering the submission strategy, you integrate smoothly with the system while upholding the crucial principle of consent. This API offers even more capabilities than what meets the eye. In iPadOS and iOS 26, your continued processing tasks can also benefit from background GPU access on supported devices.
To take advantage of this, make sure you add the background GPU capability in your Xcode project settings.
Once that’s added, you can and should, dynamically query the scheduler’s supported resources property. This lets your app understand exactly what the current device supports at runtime so you can adapt your task requirements appropriately.
This check is important. The system enforces these requirements. Any requests for unavailable resources will be rejected upon submission, keeping the system and your task in a healthy, known state.
Finally, remember the broader context of system priorities. iOS prioritizes the foreground experience, meaning your background task may receive a lower quality of service compared to when your app is active. However, the system is smart about this. When your app does return to the foreground, it will intelligently boost your task priority, helping ensure things remain smooth and responsive.
These new APIs enable the most powerful and seamless background execution capabilities yet, adding the vital BGContinuedProcessingTask to your toolkit. With a complete understanding of these tasks and how they fit into the broader system, you're ready to start adopting them. We’re excited to see how you’ll use these tools to create smarter, more efficient background experiences. Thank you for watching!
-
-
8:27 - Register an app refresh task
import BackgroundTasks import SwiftUI @main struct ColorFeed: App { var body: some Scene { WindowGroup { // ... } .backgroundTask(.appRefresh("com.colorfeed.wwdc25.appRefresh")) { await self.handleAppRefreshTask() } } }
-
9:45 - Register a processing task
import BackgroundTasks import UIKit class AppDelegate: UIResponder, UIApplicationDelegate { func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { BGTaskScheduler.shared.register( forTaskWithIdentifier: "com.example.apple-samplecode.ColorFeed.db_cleaning", using: nil ) { task in self.handleAppRefresh(task: task as! BGProcessingTask) } } func submitProcessingTaskRequest() { let request = BGProcessingTaskRequest( identifier: "com.example.apple-samplecode.ColorFeed.db_cleaning" ) request.requiresNetworkConnectivity = true request.requiresExternalPower = true BGTaskScheduler.shared.submit(request)! } }
-
10:51 - Begin and end background task
import UIKit @main class AppDelegate: UIResponder, UIApplicationDelegate { var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid func saveState() { /* ... */ } func handlePersistence() { let app = UIApplication.shared guard backgroundTaskID != .invalid else { return } backgroundTaskID = app.beginBackgroundTask(withName: "Finish Export") { app.endBackgroundTask(self.backgroundTaskID) self.backgroundTaskID = .invalid } self.saveState() app.endBackgroundTask(backgroundTaskID) backgroundTaskID = .invalid } }
-
14:00 - Continued processing task registration
import BackgroundTasks func handleDialogConfirmation() { BGTaskScheduler.shared.register("com.colorfeed.wwdc25.userTask") { task in let task = task as! BGContinuedProcessingTask var shouldContinue = true task.expirationHandler = { shouldContinue = false } task.progress.totalUnitCount = 100 task.progress.completedUnitCount = 0 while shouldContinue { // Do some work task.progress.completedUnitCount += 1 } task.setTaskCompleted(success: true) } }
-
15:47 - Continued processing task submission
import BackgroundTasks func submitContinuedProcessingTaskRequest() { let request = BGContinuedProcessingTaskRequest( identifier: "com.colorfeed.wwdc25.userTask", title: "A succinct title", subtitle: "A useful and informative subtitle" ) request.strategy = .fail BGTaskScheduler.shared.submit(request)! }
-
-
- 0:00 - Welcome
Learn how apps can use background runtime to perform tasks like prefetching and syncing in your app.
- 1:22 - Introduction
Apps load into memory, become foregrounded, and are the main focus. When someone switches away from an app, it goes to the background, and the system suspends it to save battery and resources. The app can request a brief time to finish tasks. Upon return, it's resumed to the foreground.
- 2:09 - Behaviors and constraints
In iOS and iPadOS, background runtime is tightly managed to prioritize battery life and optimize performance. Background execution is opportunistic and discretionary, with energy being the most fundamental constraint. You should design your app's background tasks to be efficient, lightweight, and purpose-driven. The system coalesces work and may throttle, suspend, or terminate processes that consume too many resources. Keep background work minimal, defer nonessential tasks until charging, and ensure tasks are atomic and resilient. Apps should be courteous, honoring people's preferences and the system conditions. The system provides transparency to people, allowing them to influence scheduling decisions. The success of background workloads depends on their adaptability and alignment with the system's priorities.
- 7:29 - Background task APIs
iOS and iPadOS offer various APIs for designing tasks that run in the background. The system optimizes these tasks based on app usage patterns and device constraints. 'BGAppRefreshTask' allows apps to silently fetch content before use, with frequently used apps receiving more frequent scheduling. 'Background Push Notifications' wake the app to fetch new content when the server sends a notification, but these are considered discretionary and low-priority to minimize overhead. 'BGProcessingTask' enables apps to perform more complex work like running ML models or database maintenance. This task needs to be registered during launch, and you can configure it to run only when the device is on a charger and connected to a network. beginBackgroundTask and endBackgroundTask APIs give apps extra time to complete crucial work, such as saving state or closing connections, when transitioning to the background.
- 11:23 - Continued processing tasks
In iPadOS and iOS 26, there's a new 'BGContinuedProcessingTask' API, which allows apps to complete tasks started by someone in the foreground even after being backgrounded. This feature enhances the experience by enabling the app to complete complex tasks without the app needing to be open. For example, the Journal app uses this API to export files in the background. Someone initiates the export with a button tap, and the app then provides progress updates through the system UI. The person can monitor the progress and cancel the task at any time, maintaining complete control. These tasks always need to start with an explicit action, such as a button tap or gesture. They represent clear and immediate goals, like exporting files, publishing social media content, or completing updates on connected accessories. The system expects these tasks to make measurable progress. You need to register a launch handler to manage state and progress reporting. Provide timely updates about the workload progression using the progress-reporting protocol. If the task's progress is slower than expected, the system prompts the person to decide whether to continue. When the task completes, you need to inform the system by using the 'setTaskCompleted' method. Additionally, the app must handle potential interruptions gracefully by providing an expiration handler. The API also supports background GPU access on supported devices, allowing you to create more powerful and efficient background experiences. However, remember that iOS prioritizes the foreground experience, and background tasks may receive a lower quality of service. Nevertheless, the system intelligently boosts the task priority when the app returns to the foreground.