使用 BGTaskScheduler 在 iOS 上调度和执行后台工作。适用于注册 BGAppRefreshTask 进行短时后台获取、BGProcessingTask 进行长时间维护、BGContinuedProcessingTask(iOS 26+)进行前台启动并在后台继续的工作、后台 URLSession 下载或后台推送通知。涵盖 Info.plist 配置、过期处理、任务完成以及使用模拟启动进行调试。
后台处理
使用 BackgroundTasks 框架、后台 URLSession 和后台推送通知在 iOS 上注册、调度和执行后台工作。
目录
- Info.plist 配置
- BGTaskScheduler 注册
- BGAppRefreshTask 模式
- BGProcessingTask 模式
- BGContinuedProcessingTask(iOS 26+)
- 后台 URLSession 下载
- 后台推送触发
- 常见错误
- 审查清单
- 参考
Info.plist 配置
每个任务标识符必须在 Info.plist 的 BGTaskSchedulerPermittedIdentifiers 中声明,否则 submit(_:) 会抛出 BGTaskScheduler.Error.Code.notPermitted。
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.example.app.refresh</string>
<string>com.example.app.db-cleanup</string>
<string>com.example.app.export.*</string>
</array>
同时启用所需的 UIBackgroundModes:
<key>UIBackgroundModes</key>
<array>
<string>fetch</string> <!-- BGAppRefreshTask 需要 -->
<string>processing</string> <!-- BGProcessingTask 需要 -->
</array>
在 Xcode 中:目标 > Signing & Capabilities > Background Modes > 启用 "Background fetch" 和 "Background processing"。
BGTaskScheduler 注册
在应用启动完成之前注册处理程序。在 UIKit 中,在 application(_:didFinishLaunchingWithOptions:) 中注册;在 SwiftUI 中,在 App.init() 中注册。
UIKit 注册
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 注册
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 模式
短时任务(约 30 秒),用于获取小数据更新。系统决定何时启动;earliestBeginDate 仅作为下限提示。
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 模式
长时间任务(数分钟),用于维护、数据处理或清理。它们在设备空闲时运行,可能需要外部电源;同样适用 earliestBeginDate 下限规则。
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+)
由用户操作在前台启动并在后台继续运行的任务。系统通过 Live Activity 显示进度。遵循 ProgressReporting。
可用性: iOS 26.0+,iPadOS 26.0+
与 BGAppRefreshTask 和 BGProcessingTask 不同,此任务从前台立即启动。系统可以在资源压力下终止它,优先终止报告进度最少的任务。设置 expirationHandler 以处理用户或系统取消,取消进行中的工作,并在报告完成前清理部分输出。
import BackgroundTasks
func startExport() {
// 在应用启动时注册任务处理程序,而不是在这里。
// BGTaskScheduler 要求在应用启动完成前注册。
let jobID = UUID().uuidString
let request = BGContinuedProcessingTaskRequest(
identifier: "com.example.app.export.\(jobID)",
title: "导出照片",
subtitle: "正在处理 247 个项目"
)
// 使用允许的基础通配符标识符:com.example.app.export.*
// earliestBeginDate 对于继续处理请求被忽略。
// .queue:如果无法立即运行,则尽快开始
// .fail:如果无法立即运行,则提交失败
request.strategy = .queue
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(
"导出照片",
subtitle: "已完成 \(index + 1) / \(items.count)"
)
}
task.setTaskCompleted(success: !Task.isCancelled)
}
对于 GPU 工作,检查支持并启用后台 GPU 访问(com.apple.developer.background-tasks.continued-processing.gpu):
let supported = BGTaskScheduler.supportedResources
if supported.contains(.gpu) {
request.requiredResources = .gpu
}
后台 URLSession 下载
使用 URLSessionConfiguration.background 进行即使在应用挂起或终止后仍继续的下载。系统在进程外处理传输。
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)") }
}
}
处理应用重新启动——存储并调用系统完成处理程序:
// 在 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
}
}
后台推送触发
静默推送通知会短暂唤醒您的应用以获取新内容。在推送负载中设置 content-available: 1。
{ "aps": { "content-available": 1 }, "custom-data": "new-messages" }
发送 APNs 请求时使用 apns-push-type: background 和 apns-priority: 5。后台推送投递优先级较低且不保证送达;保持发送频率较低,通常每小时不超过两到三次。
在 AppDelegate 中处理:
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)
}
}
}
在 Background Modes 中启用 "Remote notifications" 并注册:
UIApplication.shared.registerForRemoteNotifications()
常见错误
1. 缺少 Info.plist 标识符
// 不要:提交标识符不在 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. 未调用 setTaskCompleted(success:)
使用上述规范的应用刷新或处理处理程序:每个成功、失败和取消路径都恰好报告一次完成。
3. 忽略过期处理程序
使用相同的规范处理程序在 expirationHandler 中取消进行中的工作并报告失败。
4. 调度过于频繁
调度部分拥有下限规则。避免分钟级的刷新请求;系统仍然选择实际的启动时间。
5. 过度依赖后台时间
// 不要:假设 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)
}
}
审查清单
- [ ] 所有任务标识符已列在
BGTaskSchedulerPermittedIdentifiers中 - [ ] 已启用所需的
UIBackgroundModes(fetch、processing) - [ ] 任务在应用启动完成前注册
- [ ] 每个代码路径都调用了
setTaskCompleted(success:) - [ ] 设置了
expirationHandler并取消进行中的工作 - [ ] 在处理程序内部调度下一个任务(重新调度模式)
- [ ]
earliestBeginDate使用合理的间隔并视为提示 - [ ] 后台 URLSession 使用委托(而非 async/闭包)
- [ ] 后台 URLSession 文件在
didFinishDownloadingTo返回前移动 - [ ]
handleEventsForBackgroundURLSession存储并调用完成处理程序 - [ ] 后台推送负载包含
content-available: 1 - [ ] 后台推送 APNs 请求使用
apns-push-type: background和apns-priority: 5 - [ ] 及时调用
fetchCompletionHandler并返回正确结果 - [ ] BGContinuedProcessingTask 通过
ProgressReporting报告进度 - [ ] 工作是增量且可取消的(
Task.checkCancellation()) - [ ] 任务处理程序中无阻塞同步工作
参考
- 参见 references/background-task-patterns.md 了解扩展模式、后台 URLSession 边缘情况、使用模拟启动进行调试以及后台推送最佳实践。
- BGTaskScheduler
- BGAppRefreshTask
- BGProcessingTask
- BGContinuedProcessingTask(iOS 26+)
- BGContinuedProcessingTaskRequest(iOS 26+)
- 使用后台任务更新您的应用
- 在 iOS 和 iPadOS 上执行长时间运行的任务






