background-processing by dpearson2699/swift-ios-skills
npx skills add https://github.com/dpearson2699/swift-ios-skills --skill background-processing使用 BackgroundTasks 框架、后台 URLSession 和后台推送通知,在 iOS 上注册、调度和执行后台工作。
每个任务标识符必须在 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>
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
同时启用所需的 UIBackgroundModes:
<key>UIBackgroundModes</key>
<array>
<string>fetch</string> <!-- BGAppRefreshTask 所需 -->
<string>processing</string> <!-- BGProcessingTask 所需 -->
</array>
在 Xcode 中:target > Signing & Capabilities > Background Modes > 启用 "Background fetch" 和 "Background processing"。
在应用启动完成之前注册处理器。在 UIKit 中,在 application(_:didFinishLaunchingWithOptions:) 中注册。在 SwiftUI 中,在 App 初始化器中注册。
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
}
}
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() }
}
}
用于获取少量数据更新的短时任务(约 30 秒)。系统根据使用模式决定何时启动。
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) {
// 在执行工作前调度下一次刷新
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)
}
}
用于维护、数据处理或清理的长时间运行任务(数分钟)。仅在设备空闲且(可选)充电时运行。
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)
}
}
由用户操作在前台启动,然后在后台继续运行的任务。系统通过 Live Activity 显示进度。遵循 ProgressReporting 协议。
可用性: iOS 26.0+, iPadOS 26.0+
与 BGAppRefreshTask 和 BGProcessingTask 不同,此任务立即从前台开始。系统可能在资源压力下终止它,优先终止报告进度最小的任务。
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("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)
// 更新面向用户的标题/副标题
task.updateTitle(
"Exporting Photos",
subtitle: "\(index + 1) of \(items.count) complete"
)
}
task.setTaskCompleted(success: !Task.isCancelled)
}
检查系统是否支持你的任务所需的资源:
let supported = BGTaskScheduler.shared.supportedResources
if supported.contains(.gpu) {
request.requiredResources = .gpu
}
使用 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
) {
// 在此方法返回前将文件从 tmp 移动
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)") }
}
}
处理应用重新启动——存储并调用系统完成处理程序:
// 在 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" }
在 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()
// 错误做法:提交一个标识符不在 BGTaskSchedulerPermittedIdentifiers 中的任务
let request = BGAppRefreshTaskRequest(identifier: "com.example.app.refresh")
try BGTaskScheduler.shared.submit(request) // 抛出 .notPermitted
// 正确做法:将每个标识符添加到 Info.plist 的 BGTaskSchedulerPermittedIdentifiers
// <string>com.example.app.refresh</string>
// 错误做法:未标记完成就返回——系统会惩罚未来的调度
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)
}
}
// 错误做法:假设你的任务会运行完成
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)
}
}
// 错误做法:每分钟请求刷新——系统会积极限制
request.earliestBeginDate = Date(timeIntervalSinceNow: 60)
// 正确做法:使用合理的间隔(刷新 15 分钟以上)
request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
// earliestBeginDate 是一个提示——系统选择实际的启动时间
// 错误做法:假设一个 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 使用合理的间隔(刷新 15 分钟以上)didFinishDownloadingTo 中返回前已移动handleEventsForBackgroundURLSession 存储并调用完成处理程序content-available: 1fetchCompletionHandler 并返回正确结果ProgressReporting 报告进度Task.checkCancellation())references/background-task-patterns.md 以获取扩展模式、后台 URLSession 边缘情况、使用模拟启动进行调试以及后台推送最佳实践。每周安装数
338
代码仓库
GitHub 星标数
269
首次出现
2026年3月8日
安全审计
安装于
codex335
cursor332
amp332
cline332
github-copilot332
kimi-cli332
Register, schedule, and execute background work on iOS using the BackgroundTasks framework, background URLSession, and background push notifications.
Every task identifier must be declared in Info.plist under BGTaskSchedulerPermittedIdentifiers, or submit(_:) throws 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>
Also enable the required UIBackgroundModes:
<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".
Register handlers before app launch completes. In UIKit, register in application(_:didFinishLaunchingWithOptions:). In SwiftUI, register in the App initializer.
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
}
}
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() }
}
}
Short-lived tasks (~30 seconds) for fetching small data updates. The system decides when to launch based on usage patterns.
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)
}
}
Long-running tasks (minutes) for maintenance, data processing, or cleanup. Runs only when device is idle and (optionally) charging.
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)
}
}
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 ProgressReporting.
Availability: iOS 26.0+, iPadOS 26.0+
Unlike BGAppRefreshTask and BGProcessingTask, this task starts immediately from the foreground. The system can terminate it under resource pressure, prioritizing tasks that report minimal progress first.
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:
let supported = BGTaskScheduler.shared.supportedResources
if supported.contains(.gpu) {
request.requiredResources = .gpu
}
Use URLSessionConfiguration.background for downloads that continue even after the app is suspended or terminated. The system handles the transfer out of process.
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:
// 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
}
}
Silent push notifications wake your app briefly to fetch new content. Set content-available: 1 in the push payload.
{ "aps": { "content-available": 1 }, "custom-data": "new-messages" }
Handle in 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)
}
}
}
Enable "Remote notifications" in Background Modes and register:
UIApplication.shared.registerForRemoteNotifications()
// 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>
// 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)
}
}
// 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)
}
}
// 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 time
// 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)
}
}
BGTaskSchedulerPermittedIdentifiersUIBackgroundModes enabled (fetch, processing)setTaskCompleted(success:) called on every code pathexpirationHandler set and cancels in-flight workearliestBeginDate uses reasonable intervals (15+ min for refresh)didFinishDownloadingTo before returnhandleEventsForBackgroundURLSession stores and calls completion handlerreferences/background-task-patterns.md for extended patterns, background URLSession edge cases, debugging with simulated launches, and background push best practices.Weekly Installs
338
Repository
GitHub Stars
269
First Seen
Mar 8, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
codex335
cursor332
amp332
cline332
github-copilot332
kimi-cli332
GSAP 框架集成指南:Vue、Svelte 等框架中 GSAP 动画最佳实践
1,200 周安装
Rust调用关系图生成器 - 可视化函数调用层次结构,提升代码分析效率
539 周安装
parallel-web-extract:并行网页内容提取工具,高效抓取网页数据
595 周安装
腾讯云CloudBase AI模型Web技能:前端调用混元/DeepSeek模型,实现流式文本生成
560 周安装
Apollo Connectors 模式助手:GraphQL API 连接器开发与集成指南
565 周安装
GitHub Trending 趋势分析工具:实时发现热门项目、技术洞察与开源机会
556 周安装
GSAP React 集成教程:useGSAP Hook 动画库与 React 组件开发指南
546 周安装
content-available: 1fetchCompletionHandler called promptly with correct resultProgressReportingTask.checkCancellation())