关于 background-processing
面向 iOS 开发者的后台处理技能,讲解使用 BackgroundTasks、后台 URLSession 和后台推送安排与执行任务,并涵盖配置、取消处理及常见问题。
主要功能
- 01配置 BGTaskScheduler 与任务标识符
- 02安排刷新和处理任务
- 03使用持续处理任务执行前台启动工作
- 04通过后台 URLSession 下载
- 05用后台推送触发内容更新
- 06处理过期、取消与任务完成
适用场景
- 01为应用安排周期性数据刷新
- 02在设备空闲时执行维护或数据处理
- 03让下载在应用暂停或终止后继续
- 04收到静默推送后获取新内容
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dpearson2699/ swift-ios-skills --skill background-processing安装后在 Claude Code 中输入/background-processing确认已加载。
附带文件evals/evals.jsonreferences/background-task-patterns.md
详细介绍
后台处理
使用 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>
<string>processing</string>
</array>
在 Xcode 中:target > 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 = default background 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
}
}
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("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)
}
}
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("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)
}
}
BGContinuedProcessingTask(iOS 26+)
由用户操作在前台启动、并在后台继续运行的任务。系统通过 Live Activity 显示进度。遵循 ProgressReporting。
可用性: iOS 26.0+、iPadOS 26.0+
与 BGAppRefreshTask 和 BGProcessingTask 不同,此任务会立即从前台启动。系统可能会因资源压力而终止任务,并优先终止报告进度最少的任务。请设置 expirationHandler 以处理用户或系统取消,取消正在进行的工作,并在报告完成前清理部分输出。
import BackgroundTasks
func startExport() {
// Register the task handler at app launch, not here.
// BGTaskScheduler requires registration before app launch completes.
let jobID = UUID().uuidString
let request = BGContinuedProcessingTaskRequest(
identifier: "com.example.app.export.\(jobID)",
title: "Exporting Photos",
subtitle: "Processing 247 items"
)
// Use a permitted base wildcard identifier: com.example.app.export.*
// earliestBeginDate is ignored for continued processing requests.
// .queue: begin as soon as possible if can't run immediately
// .fail: fail submission if can't run immediately
request.strategy = .queue
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)
}
对于 GPU 工作,请检查是否受支持,并启用 Background GPU Access(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
) {
// 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)") }
}
}
处理应用重新启动的情况——存储并调用系统完成处理程序:
// 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
}
}
后台推送触发器
静默推送通知会短暂唤醒应用以获取新内容。在推送负载中设置
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 标识符
// 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>
2. 未调用 setTaskCompleted(success:)
使用上面的标准应用刷新或处理程序:每条成功、失败和取消路径都要恰好报告一次完成状态。
3. 忽略过期处理程序
使用同一标准处理程序取消正在进行的工作,并在
expirationHandler 中报告失败。
4. 安排任务过于频繁
安排任务部分规定了最早启动时间规则。避免以分钟为间隔请求刷新;实际启动时间仍由系统决定。
5. 过度依赖后台运行时间
// 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)
}
}
审查清单
-
BGTaskSchedulerPermittedIdentifiers中列出所有任务标识符 - 已启用必需的
UIBackgroundModes(fetch、processing) - 在应用启动完成前注册任务
- 在每条代码路径上调用
setTaskCompleted(success:) - 设置
expirationHandler并取消正在进行的工作 - 在处理程序内安排下一个任务(重新安排模式)
-
earliestBeginDate使用合理的时间间隔,并将其视为提示 - 后台 URLSession 使用代理(而非 async/闭包)
- 在
didFinishDownloadingTo中于返回前移动后台 URLSession 文件 -
handleEventsForBackgroundURLSession保存并调用完成处理程序 - 后台推送载荷包含
content-available: 1 - 后台推送 APNs 请求使用
apns-push-type: background和apns-priority: 5 - 及时调用
fetchCompletionHandler并传入正确的结果 - BGContinuedProcessingTask 通过
ProgressReporting报告进度 - 工作采用增量方式且可安全取消(
Task.checkCancellation()) - 任务处理程序中不执行会阻塞的同步工作
参考资料
- 有关扩展模式、后台 URLSession 的边缘情况、通过模拟启动进行调试以及后台推送最佳实践,请参阅 references/background-task-patterns.md。
- BGTaskScheduler
- BGAppRefreshTask
- BGProcessingTask
- BGContinuedProcessingTask(iOS 26+)
- BGContinuedProcessingTaskRequest(iOS 26+)
- 使用后台任务更新应用
- 在 iOS 和 iPadOS 上执行长时间运行的任务