background-processing
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseBackground Processing
后台任务处理
Register, schedule, and execute background work on iOS using the BackgroundTasks
framework, background URLSession, and background push notifications.
使用BackgroundTasks框架、后台URLSession和后台推送通知,在iOS上注册、调度并执行后台任务。
Contents
目录
Info.plist Configuration
Info.plist 配置
Every task identifier must be declared in under
, or throws
.
Info.plistBGTaskSchedulerPermittedIdentifierssubmit(_:)BGTaskScheduler.Error.Code.notPermittedxml
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.example.app.refresh</string>
<string>com.example.app.db-cleanup</string>
<string>com.example.app.export</string>
</array>Also enable the required :
UIBackgroundModesxml
<key>UIBackgroundModes</key>
<array>
<string>fetch</string> <!-- Required for BGAppRefreshTask -->
<string>processing</string> <!-- Required for BGProcessingTask -->
</array>In Xcode: target > Signing & Capabilities > Background Modes > enable
"Background fetch" and "Background processing".
每个任务标识符必须在的中声明,否则调用会抛出错误。
Info.plistBGTaskSchedulerPermittedIdentifierssubmit(_:)BGTaskScheduler.Error.Code.notPermittedxml
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.example.app.refresh</string>
<string>com.example.app.db-cleanup</string>
<string>com.example.app.export</string>
</array>同时需要启用所需的:
UIBackgroundModesxml
<key>UIBackgroundModes</key>
<array>
<string>fetch</string> <!-- BGAppRefreshTask 必需 -->
<string>processing</string> <!-- BGProcessingTask 必需 -->
</array>在Xcode中的操作:目标项目 > 签名与功能 > 后台模式 > 启用“后台获取”和“后台处理”。
BGTaskScheduler Registration
BGTaskScheduler 注册
Register handlers before app launch completes. In UIKit, register in
. In SwiftUI, register in the
initializer.
application(_:didFinishLaunchingWithOptions:)App需在应用启动完成之前注册任务处理器。在UIKit中,在方法中注册;在SwiftUI中,在初始化器中注册。
application(_:didFinishLaunchingWithOptions:)AppUIKit Registration
UIKit 注册
swift
import BackgroundTasks
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
BGTaskScheduler.shared.register(
forTaskWithIdentifier: "com.example.app.refresh",
using: nil // nil = main queue
) { task in
self.handleAppRefresh(task: task as! BGAppRefreshTask)
}
BGTaskScheduler.shared.register(
forTaskWithIdentifier: "com.example.app.db-cleanup",
using: nil
) { task in
self.handleDatabaseCleanup(task: task as! BGProcessingTask)
}
return true
}
}swift
import BackgroundTasks
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
BGTaskScheduler.shared.register(
forTaskWithIdentifier: "com.example.app.refresh",
using: nil // nil = 主队列
) { task in
self.handleAppRefresh(task: task as! BGAppRefreshTask)
}
BGTaskScheduler.shared.register(
forTaskWithIdentifier: "com.example.app.db-cleanup",
using: nil
) { task in
self.handleDatabaseCleanup(task: task as! BGProcessingTask)
}
return true
}
}SwiftUI Registration
SwiftUI 注册
swift
import SwiftUI
import BackgroundTasks
@main
struct MyApp: App {
init() {
BGTaskScheduler.shared.register(
forTaskWithIdentifier: "com.example.app.refresh",
using: nil
) { task in
BackgroundTaskManager.shared.handleAppRefresh(
task: task as! BGAppRefreshTask
)
}
}
var body: some Scene {
WindowGroup { ContentView() }
}
}swift
import SwiftUI
import BackgroundTasks
@main
struct MyApp: App {
init() {
BGTaskScheduler.shared.register(
forTaskWithIdentifier: "com.example.app.refresh",
using: nil
) { task in
BackgroundTaskManager.shared.handleAppRefresh(
task: task as! BGAppRefreshTask
)
}
}
var body: some Scene {
WindowGroup { ContentView() }
}
}BGAppRefreshTask Patterns
BGAppRefreshTask 使用模式
Short-lived tasks (~30 seconds) for fetching small data updates. The system
decides when to launch based on usage patterns.
swift
func scheduleAppRefresh() {
let request = BGAppRefreshTaskRequest(
identifier: "com.example.app.refresh"
)
request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
do {
try BGTaskScheduler.shared.submit(request)
} catch {
print("Could not schedule app refresh: \(error)")
}
}
func handleAppRefresh(task: BGAppRefreshTask) {
// Schedule the next refresh before doing work
scheduleAppRefresh()
let fetchTask = Task {
do {
let data = try await APIClient.shared.fetchLatestFeed()
await FeedStore.shared.update(with: data)
task.setTaskCompleted(success: true)
} catch {
task.setTaskCompleted(success: false)
}
}
// CRITICAL: Handle expiration -- system can revoke time at any moment
task.expirationHandler = {
fetchTask.cancel()
task.setTaskCompleted(success: false)
}
}短时长任务(约30秒),用于获取小型数据更新。系统会根据用户使用模式决定何时启动任务。
swift
func scheduleAppRefresh() {
let request = BGAppRefreshTaskRequest(
identifier: "com.example.app.refresh"
)
request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
do {
try BGTaskScheduler.shared.submit(request)
} catch {
print("无法调度应用刷新任务:\(error)")
}
}
func handleAppRefresh(task: BGAppRefreshTask) {
// 在执行任务前调度下一次刷新
scheduleAppRefresh()
let fetchTask = Task {
do {
let data = try await APIClient.shared.fetchLatestFeed()
await FeedStore.shared.update(with: data)
task.setTaskCompleted(success: true)
} catch {
task.setTaskCompleted(success: false)
}
}
// 关键:处理任务过期 —— 系统可能随时收回任务执行时间
task.expirationHandler = {
fetchTask.cancel()
task.setTaskCompleted(success: false)
}
}BGProcessingTask Patterns
BGProcessingTask 使用模式
Long-running tasks (minutes) for maintenance, data processing, or cleanup.
Runs only when device is idle and (optionally) charging.
swift
func scheduleProcessingTask() {
let request = BGProcessingTaskRequest(
identifier: "com.example.app.db-cleanup"
)
request.requiresNetworkConnectivity = false
request.requiresExternalPower = true
request.earliestBeginDate = Date(timeIntervalSinceNow: 60 * 60)
do {
try BGTaskScheduler.shared.submit(request)
} catch {
print("Could not schedule processing task: \(error)")
}
}
func handleDatabaseCleanup(task: BGProcessingTask) {
scheduleProcessingTask()
let cleanupTask = Task {
do {
try await DatabaseManager.shared.purgeExpiredRecords()
try await DatabaseManager.shared.rebuildIndexes()
task.setTaskCompleted(success: true)
} catch {
task.setTaskCompleted(success: false)
}
}
task.expirationHandler = {
cleanupTask.cancel()
task.setTaskCompleted(success: false)
}
}长时长任务(数分钟),用于执行维护、数据处理或清理工作。仅在设备空闲且(可选)充电时运行。
swift
func scheduleProcessingTask() {
let request = BGProcessingTaskRequest(
identifier: "com.example.app.db-cleanup"
)
request.requiresNetworkConnectivity = false
request.requiresExternalPower = true
request.earliestBeginDate = Date(timeIntervalSinceNow: 60 * 60)
do {
try BGTaskScheduler.shared.submit(request)
} catch {
print("无法调度处理任务:\(error)")
}
}
func handleDatabaseCleanup(task: BGProcessingTask) {
scheduleProcessingTask()
let cleanupTask = Task {
do {
try await DatabaseManager.shared.purgeExpiredRecords()
try await DatabaseManager.shared.rebuildIndexes()
task.setTaskCompleted(success: true)
} catch {
task.setTaskCompleted(success: false)
}
}
task.expirationHandler = {
cleanupTask.cancel()
task.setTaskCompleted(success: false)
}
}BGContinuedProcessingTask (iOS 26+)
BGContinuedProcessingTask(iOS 26+)
A task initiated in the foreground by a user action that continues running in the
background. The system displays progress via a Live Activity. Conforms to
.
ProgressReportingAvailability: iOS 26.0+, iPadOS 26.0+
Unlike and , this task starts immediately
from the foreground. The system can terminate it under resource pressure,
prioritizing tasks that report minimal progress first.
BGAppRefreshTaskBGProcessingTaskswift
import BackgroundTasks
func startExport() {
let request = BGContinuedProcessingTaskRequest(
identifier: "com.example.app.export",
title: "Exporting Photos",
subtitle: "Processing 247 items"
)
// .queue: begin as soon as possible if can't run immediately
// .fail: fail submission if can't run immediately
request.strategy = .queue
BGTaskScheduler.shared.register(
forTaskWithIdentifier: "com.example.app.export",
using: nil
) { task in
let continuedTask = task as! BGContinuedProcessingTask
Task {
await self.performExport(task: continuedTask)
}
}
do {
try BGTaskScheduler.shared.submit(request)
} catch {
print("Could not submit continued processing task: \(error)")
}
}
func performExport(task: BGContinuedProcessingTask) async {
let items = await PhotoLibrary.shared.itemsToExport()
let progress = task.progress
progress.totalUnitCount = Int64(items.count)
for (index, item) in items.enumerated() {
if Task.isCancelled { break }
await PhotoExporter.shared.export(item)
progress.completedUnitCount = Int64(index + 1)
// Update the user-facing title/subtitle
task.updateTitle(
"Exporting Photos",
subtitle: "\(index + 1) of \(items.count) complete"
)
}
task.setTaskCompleted(success: !Task.isCancelled)
}Check whether the system supports the resources your task needs:
swift
let supported = BGTaskScheduler.shared.supportedResources
if supported.contains(.gpu) {
request.requiredResources = .gpu
}由用户在前台触发、随后延续到后台执行的任务。系统会通过Live Activity展示任务进度,该任务遵循协议。
ProgressReporting兼容性: iOS 26.0+、iPadOS 26.0+
与和不同,此任务从前台立即启动。在资源紧张时,系统可能终止任务,优先保留报告进度最少的任务。
BGAppRefreshTaskBGProcessingTaskswift
import BackgroundTasks
func startExport() {
let request = BGContinuedProcessingTaskRequest(
identifier: "com.example.app.export",
title: "Exporting Photos",
subtitle: "Processing 247 items"
)
// .queue:如果无法立即运行,就尽快启动
// .fail:如果无法立即运行,就提交失败
request.strategy = .queue
BGTaskScheduler.shared.register(
forTaskWithIdentifier: "com.example.app.export",
using: nil
) { task in
let continuedTask = task as! BGContinuedProcessingTask
Task {
await self.performExport(task: continuedTask)
}
}
do {
try BGTaskScheduler.shared.submit(request)
} catch {
print("无法提交延续处理任务:\(error)")
}
}
func performExport(task: BGContinuedProcessingTask) async {
let items = await PhotoLibrary.shared.itemsToExport()
let progress = task.progress
progress.totalUnitCount = Int64(items.count)
for (index, item) in items.enumerated() {
if Task.isCancelled { break }
await PhotoExporter.shared.export(item)
progress.completedUnitCount = Int64(index + 1)
// 更新面向用户的标题/副标题
task.updateTitle(
"Exporting Photos",
subtitle: "\(index + 1) of \(items.count) complete"
)
}
task.setTaskCompleted(success: !Task.isCancelled)
}检查系统是否支持任务所需资源:
swift
let supported = BGTaskScheduler.shared.supportedResources
if supported.contains(.gpu) {
request.requiredResources = .gpu
}Background URLSession Downloads
后台URLSession 下载
Use for downloads that continue even after
the app is suspended or terminated. The system handles the transfer out of
process.
URLSessionConfiguration.backgroundswift
class DownloadManager: NSObject, URLSessionDownloadDelegate {
static let shared = DownloadManager()
private lazy var session: URLSession = {
let config = URLSessionConfiguration.background(
withIdentifier: "com.example.app.background-download"
)
config.isDiscretionary = true
config.sessionSendsLaunchEvents = true
return URLSession(configuration: config, delegate: self, delegateQueue: nil)
}()
func startDownload(from url: URL) {
let task = session.downloadTask(with: url)
task.earliestBeginDate = Date(timeIntervalSinceNow: 60)
task.resume()
}
func urlSession(
_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didFinishDownloadingTo location: URL
) {
// Move file from tmp before this method returns
let dest = FileManager.default.urls(
for: .documentDirectory, in: .userDomainMask
)[0].appendingPathComponent("download.dat")
try? FileManager.default.moveItem(at: location, to: dest)
}
func urlSession(
_ session: URLSession,
task: URLSessionTask,
didCompleteWithError error: (any Error)?
) {
if let error { print("Download failed: \(error)") }
}
}Handle app relaunch — store and invoke the system completion handler:
swift
// In AppDelegate:
func application(
_ application: UIApplication,
handleEventsForBackgroundURLSession identifier: String,
completionHandler: @escaping () -> Void
) {
backgroundSessionCompletionHandler = completionHandler
}
// In URLSessionDelegate — call stored handler when events finish:
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
Task { @MainActor in
self.backgroundSessionCompletionHandler?()
self.backgroundSessionCompletionHandler = nil
}
}使用实现应用挂起或终止后仍能继续的下载任务,系统会在进程外处理传输操作。
URLSessionConfiguration.backgroundswift
class DownloadManager: NSObject, URLSessionDownloadDelegate {
static let shared = DownloadManager()
private lazy var session: URLSession = {
let config = URLSessionConfiguration.background(
withIdentifier: "com.example.app.background-download"
)
config.isDiscretionary = true
config.sessionSendsLaunchEvents = true
return URLSession(configuration: config, delegate: self, delegateQueue: nil)
}()
func startDownload(from url: URL) {
let task = session.downloadTask(with: url)
task.earliestBeginDate = Date(timeIntervalSinceNow: 60)
task.resume()
}
func urlSession(
_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didFinishDownloadingTo location: URL
) {
// 在返回前将文件从临时目录移走
let dest = FileManager.default.urls(
for: .documentDirectory, in: .userDomainMask
)[0].appendingPathComponent("download.dat")
try? FileManager.default.moveItem(at: location, to: dest)
}
func urlSession(
_ session: URLSession,
task: URLSessionTask,
didCompleteWithError error: (any Error)?
) {
if let error { print("下载失败:\(error)") }
}
}处理应用重启 —— 存储并调用系统完成处理器:
swift
// 在AppDelegate中:
func application(
_ application: UIApplication,
handleEventsForBackgroundURLSession identifier: String,
completionHandler: @escaping () -> Void
) {
backgroundSessionCompletionHandler = completionHandler
}
// 在URLSessionDelegate中 —— 事件完成时调用存储的处理器:
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
Task { @MainActor in
self.backgroundSessionCompletionHandler?()
self.backgroundSessionCompletionHandler = nil
}
}Background Push Triggers
后台推送触发
Silent push notifications wake your app briefly to fetch new content. Set
in the push payload.
content-available: 1json
{ "aps": { "content-available": 1 }, "custom-data": "new-messages" }Handle in AppDelegate:
swift
func application(
_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler:
@escaping (UIBackgroundFetchResult) -> Void
) {
Task {
do {
let hasNew = try await MessageStore.shared.fetchNewMessages()
completionHandler(hasNew ? .newData : .noData)
} catch {
completionHandler(.failed)
}
}
}Enable "Remote notifications" in Background Modes and register:
swift
UIApplication.shared.registerForRemoteNotifications()静默推送通知可短暂唤醒应用以获取新内容,需在推送 payload 中设置。
content-available: 1json
{ "aps": { "content-available": 1 }, "custom-data": "new-messages" }在AppDelegate中处理:
swift
func application(
_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler:
@escaping (UIBackgroundFetchResult) -> Void
) {
Task {
do {
let hasNew = try await MessageStore.shared.fetchNewMessages()
completionHandler(hasNew ? .newData : .noData)
} catch {
completionHandler(.failed)
}
}
}在后台模式中启用“远程通知”并完成注册:
swift
UIApplication.shared.registerForRemoteNotifications()Common Mistakes
常见错误
1. Missing Info.plist identifiers
1. 缺少Info.plist中的任务标识符
swift
// DON'T: Submit a task whose identifier isn't in BGTaskSchedulerPermittedIdentifiers
let request = BGAppRefreshTaskRequest(identifier: "com.example.app.refresh")
try BGTaskScheduler.shared.submit(request) // Throws .notPermitted
// DO: Add every identifier to Info.plist BGTaskSchedulerPermittedIdentifiers
// <string>com.example.app.refresh</string>swift
// 错误:提交的任务标识符未在BGTaskSchedulerPermittedIdentifiers中声明
let request = BGAppRefreshTaskRequest(identifier: "com.example.app.refresh")
try BGTaskScheduler.shared.submit(request) // 抛出.notPermitted错误
// 正确:将所有标识符添加到Info.plist的BGTaskSchedulerPermittedIdentifiers中
// <string>com.example.app.refresh</string>2. Not calling setTaskCompleted(success:)
2. 未调用setTaskCompleted(success:)
swift
// DON'T: Return without marking completion -- system penalizes future scheduling
func handleRefresh(task: BGAppRefreshTask) {
Task {
let data = try await fetchData()
await store.update(data)
// Missing: task.setTaskCompleted(success:)
}
}
// DO: Always call setTaskCompleted on every code path
func handleRefresh(task: BGAppRefreshTask) {
let work = Task {
do {
let data = try await fetchData()
await store.update(data)
task.setTaskCompleted(success: true)
} catch {
task.setTaskCompleted(success: false)
}
}
task.expirationHandler = {
work.cancel()
task.setTaskCompleted(success: false)
}
}swift
// 错误:未标记任务完成就返回 —— 系统会惩罚后续任务调度
func handleRefresh(task: BGAppRefreshTask) {
Task {
let data = try await fetchData()
await store.update(data)
// 缺失:task.setTaskCompleted(success:)
}
}
// 正确:在所有代码路径中都调用setTaskCompleted
func handleRefresh(task: BGAppRefreshTask) {
let work = Task {
do {
let data = try await fetchData()
await store.update(data)
task.setTaskCompleted(success: true)
} catch {
task.setTaskCompleted(success: false)
}
}
task.expirationHandler = {
work.cancel()
task.setTaskCompleted(success: false)
}
}3. Ignoring the expiration handler
3. 忽略expirationHandler
swift
// DON'T: Assume your task will run to completion
func handleCleanup(task: BGProcessingTask) {
Task { await heavyWork() }
// No expirationHandler -- system terminates ungracefully
}
// DO: Set expirationHandler to cancel work and mark completed
func handleCleanup(task: BGProcessingTask) {
let work = Task { await heavyWork() }
task.expirationHandler = {
work.cancel()
task.setTaskCompleted(success: false)
}
}swift
// 错误:假设任务会执行完成
func handleCleanup(task: BGProcessingTask) {
Task { await heavyWork() }
// 未设置expirationHandler —— 系统会强制终止任务
}
// 正确:设置expirationHandler以取消正在执行的任务
func handleCleanup(task: BGProcessingTask) {
let work = Task { await heavyWork() }
task.expirationHandler = {
work.cancel()
task.setTaskCompleted(success: false)
}
}4. Scheduling too frequently
4. 调度过于频繁
swift
// DON'T: Request refresh every minute -- system throttles aggressively
request.earliestBeginDate = Date(timeIntervalSinceNow: 60)
// DO: Use reasonable intervals (15+ minutes for refresh)
request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
// earliestBeginDate is a hint -- the system chooses actual launch timeswift
// 错误:每分钟请求一次刷新 —— 系统会严格限制
request.earliestBeginDate = Date(timeIntervalSinceNow: 60)
// 正确:使用合理的时间间隔(刷新任务间隔15分钟以上)
request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
// earliestBeginDate只是提示 —— 系统会选择实际启动时间5. Over-relying on background time
5. 过度依赖后台执行时间
swift
// DON'T: Start a 10-minute operation assuming it will finish
func handleRefresh(task: BGAppRefreshTask) {
Task { await tenMinuteSync() }
}
// DO: Design work to be incremental and cancellable
func handleRefresh(task: BGAppRefreshTask) {
let work = Task {
for batch in batches {
try Task.checkCancellation()
await processBatch(batch)
await saveBatchProgress(batch)
}
task.setTaskCompleted(success: true)
}
task.expirationHandler = {
work.cancel()
task.setTaskCompleted(success: false)
}
}swift
// 错误:启动一个10分钟的操作并假设它能完成
func handleRefresh(task: BGAppRefreshTask) {
Task { await tenMinuteSync() }
}
// 正确:将任务设计为可增量执行且可取消
func handleRefresh(task: BGAppRefreshTask) {
let work = Task {
for batch in batches {
try Task.checkCancellation()
await processBatch(batch)
await saveBatchProgress(batch)
}
task.setTaskCompleted(success: true)
}
task.expirationHandler = {
work.cancel()
task.setTaskCompleted(success: false)
}
}Review Checklist
审核检查清单
- All task identifiers listed in
BGTaskSchedulerPermittedIdentifiers - Required enabled (
UIBackgroundModes,fetch)processing - Tasks registered before app launch completes
- called on every code path
setTaskCompleted(success:) - set and cancels in-flight work
expirationHandler - Next task scheduled inside the handler (re-schedule pattern)
- uses reasonable intervals (15+ min for refresh)
earliestBeginDate - Background URLSession uses delegate (not async/closures)
- Background URLSession file moved in before return
didFinishDownloadingTo - stores and calls completion handler
handleEventsForBackgroundURLSession - Background push payload includes
content-available: 1 - called promptly with correct result
fetchCompletionHandler - BGContinuedProcessingTask reports progress via
ProgressReporting - Work is incremental and cancellation-safe ()
Task.checkCancellation() - No blocking synchronous work in task handlers
- 所有任务标识符都已在中列出
BGTaskSchedulerPermittedIdentifiers - 已启用所需的(
UIBackgroundModes、fetch)processing - 任务在应用启动完成前已注册
- 在所有代码路径中都调用了
setTaskCompleted(success:) - 已设置并能取消正在执行的任务
expirationHandler - 在任务处理器内部调度了下一个任务(重新调度模式)
- 使用了合理的时间间隔(刷新任务15分钟以上)
earliestBeginDate - 后台URLSession使用了代理(而非async/闭包)
- 在方法返回前已移动后台URLSession的文件
didFinishDownloadingTo - 存储并调用了完成处理器
handleEventsForBackgroundURLSession - 后台推送payload包含
content-available: 1 - 及时调用并传入正确结果
fetchCompletionHandler - BGContinuedProcessingTask通过报告进度
ProgressReporting - 任务可增量执行且支持取消()
Task.checkCancellation() - 任务处理器中没有阻塞式同步操作
References
参考资料
- See for extended patterns, background URLSession edge cases, debugging with simulated launches, and background push best practices.
references/background-task-patterns.md - BGTaskScheduler
- BGAppRefreshTask
- BGProcessingTask
- BGContinuedProcessingTask (iOS 26+)
- BGContinuedProcessingTaskRequest (iOS 26+)
- Using background tasks to update your app
- Performing long-running tasks on iOS and iPadOS
- 查看获取扩展模式、后台URLSession边缘案例、模拟启动调试方法,以及后台推送最佳实践。
references/background-task-patterns.md - BGTaskScheduler
- BGAppRefreshTask
- BGProcessingTask
- BGContinuedProcessingTask (iOS 26+)
- BGContinuedProcessingTaskRequest (iOS 26+)
- Using background tasks to update your app
- Performing long-running tasks on iOS and iPadOS