关于 axiom-background-processing-diag
面向开发者的 iOS 后台处理症状诊断技能。内容涵盖任务未运行或提前终止、URLSession 代理问题、调度不稳定及后台启动崩溃,并提供检查和调试步骤。
主要功能
- 01诊断任务未运行的问题
- 02排查任务提前终止
- 03检查后台 URLSession 代理
- 04调查调度不稳定问题
- 05测试启动与过期处理
适用场景
- 01查明已提交的后台任务为何未启动
- 02排查任务未完成便被终止
- 03调试代理未被调用的下载任务
- 04分析开发环境与生产环境的运行差异
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ charleswiltgen/ axiom --skill axiom-background-processing-diag安装后在 Claude Code 中输入/axiom-background-processing-diag确认已加载。
详细介绍
后台处理诊断
根据症状排查后台任务问题。
相关技能:axiom-background-processing(模式、检查清单)、axiom-background-processing-ref(API 参考)
症状 1:任务从未运行
submit() 成功,但从未调用处理程序。
快速诊断(5 分钟)
Task never runs?
│
├─ Step 1: Check Info.plist (2 min)
│ ├─ BGTaskSchedulerPermittedIdentifiers contains EXACT identifier?
│ │ └─ NO → Add identifier, rebuild
│ ├─ UIBackgroundModes includes "fetch" or "processing"?
│ │ └─ NO → Add required mode
│ └─ Identifiers case-sensitive match code?
│ └─ NO → Fix typo, rebuild
│
├─ Step 2: Check registration timing (2 min)
│ ├─ Registered in didFinishLaunchingWithOptions?
│ │ └─ NO → Move registration before return true
│ └─ Registration before first submit()?
│ └─ NO → Ensure register() precedes submit()
│
└─ Step 3: Check app state (1 min)
├─ App swiped away from App Switcher?
│ └─ YES → No background until user opens app
└─ Background App Refresh disabled in Settings?
└─ YES → Enable or inform user
时间成本分析
| 方法 | 时间 | 成功率 |
|---|---|---|
| 检查 Info.plist + 注册 | 5 分钟 | 70%(可发现大多数问题) |
| 添加控制台日志 | 15 分钟 | 90% |
| 使用 LLDB 模拟启动 | 5 分钟 | 95%(确认处理程序正常工作) |
| 随机修改代码 | 2 小时以上 | 低 |
LLDB 快速测试
验证处理程序是否正确注册:
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.yourapp.refresh"]
如果断点命中 → 注册正确,问题出在调度或系统因素。 如果没有任何反应 → 注册失败。
症状 2:任务意外终止
处理程序已调用,但工作尚未完成就被终止。
快速诊断(5 分钟)
Task terminates early?
│
├─ Step 1: Check expiration handler (1 min)
│ ├─ Expiration handler set FIRST in handler?
│ │ └─ NO → Move to very first line
│ └─ Expiration handler actually cancels work?
│ └─ NO → Add cancellation logic
│
├─ Step 2: Check setTaskCompleted (2 min)
│ ├─ Called in success path?
│ ├─ Called in failure path?
│ ├─ Called after expiration?
│ └─ ANY path missing → Task never signals completion
│
├─ Step 3: Check work duration (2 min)
│ ├─ BGAppRefreshTask work > 30 seconds?
│ │ └─ YES → Chunk work or use BGProcessingTask
│ └─ BGProcessingTask work > system limit?
│ └─ YES → Save progress, resume on next launch
常见原因
| 原因 | 修复方法 |
|---|---|
| 缺少过期处理程序 | 将处理程序设为第一行 |
| 未调用 setTaskCompleted | 在所有代码路径中添加 |
| 工作耗时过长 | 分块处理并设置检查点 |
| 网络超时时间 > 任务时长 | 使用后台 URLSession |
| 过期后执行异步回调 | 检查 shouldContinue 标志 |
测试过期处理
// First simulate launch
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.yourapp.refresh"]
// Then force expiration
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateExpirationForTaskWithIdentifier:@"com.yourapp.refresh"]
验证过期处理程序是否运行,以及工作是否平稳停止。
症状 3:后台 URLSession 委托未调用
下载已完成,但从未触发 didFinishDownloadingTo。
快速诊断(5 分钟)
URLSession delegate not called?
│
├─ Step 1: Check session configuration (2 min)
│ ├─ Using URLSessionConfiguration.background()?
│ │ └─ NO → Must use background config
│ ├─ Session identifier unique?
│ │ └─ NO → Use unique bundle-prefixed ID
│ └─ sessionSendsLaunchEvents = true?
│ └─ NO → Set for app relaunch on completion
│
├─ Step 2: Check AppDelegate handler (2 min)
│ ├─ handleEventsForBackgroundURLSession implemented?
│ │ └─ NO → Required for session events
│ └─ Completion handler stored and called later?
│ └─ NO → Store handler, call after events processed
│
└─ Step 3: Check delegate assignment (1 min)
├─ Session created with delegate?
└─ Delegate not nil when task completes?
必需的 AppDelegate 代码
// Store completion handler
var backgroundSessionCompletionHandler: (() -> Void)?
func application(_ application: UIApplication,
handleEventsForBackgroundURLSession identifier: String,
completionHandler: @escaping () -> Void) {
backgroundSessionCompletionHandler = completionHandler
}
// Call after all events processed
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
DispatchQueue.main.async {
self.backgroundSessionCompletionHandler?()
self.backgroundSessionCompletionHandler = nil
}
}
症状 4:开发环境中正常,生产环境中失败
任务在调试器下运行,但在发布版本或用户设备上失败。
快速诊断(10 分钟)
Works in dev, not prod?
│
├─ Step 1: Check system constraints (3 min)
│ ├─ Low Power Mode enabled?
│ │ └─ Check ProcessInfo.isLowPowerModeEnabled
│ ├─ Background App Refresh disabled?
│ │ └─ Check UIApplication.backgroundRefreshStatus
│ └─ Battery < 20%?
│ └─ System pauses discretionary work
│
├─ Step 2: Check app state (2 min)
│ ├─ App force-quit from App Switcher?
│ │ └─ YES → No background until foreground launch
│ └─ App recently used?
│ └─ Rarely used apps get lower priority
│
├─ Step 3: Check build differences (3 min)
│ ├─ Debug vs Release optimization differences?
│ ├─ #if DEBUG code excluding production?
│ └─ Different bundle identifier in release?
│
└─ Step 4: Add production logging (2 min)
└─ Log task schedule/launch/complete to analytics
7 个调度因素
这些因素都会影响生产环境中的任务执行:
| 因素 | 检查项 |
|---|---|
| 电量严重不足 | 电量 < 20%? |
| 低电量模式 | ProcessInfo.isLowPowerModeEnabled |
| App 使用情况 | 用户是否经常打开 App? |
| App 切换器 | App 未被划掉? |
| 后台 App 刷新 | 设置中是否已启用? |
| 系统预算 | 最近是否多次在后台启动? |
| 速率限制 | 请求是否过于频繁? |
生产环境调试
添加日志以跟踪当前情况:
func scheduleRefresh() {
let request = BGAppRefreshTaskRequest(identifier: "com.app.refresh")
do {
try BGTaskScheduler.shared.submit(request)
Analytics.log("background_task_scheduled")
} catch {
Analytics.log("background_task_schedule_failed", error: error)
}
}
func handleRefresh(task: BGAppRefreshTask) {
Analytics.log("background_task_started")
// ... work ...
Analytics.log("background_task_completed")
task.setTaskCompleted(success: true)
}
症状 5:任务调度不一致
任务有时运行,但运行时间不可预测。
快速诊断(5 分钟)
Inconsistent scheduling?
│
├─ Step 1: Understand earliestBeginDate (2 min)
│ ├─ This is MINIMUM delay, not scheduled time
│ │ └─ System runs when convenient AFTER this date
│ └─ Set too far in future (> 1 week)?
│ └─ System may skip task entirely
│
├─ Step 2: Check scheduling pattern (2 min)
│ ├─ Scheduling same task multiple times?
│ │ └─ Call getPendingTaskRequests to check
│ └─ Scheduling in handler for continuity?
│ └─ Required for continuous refresh
│
└─ Step 3: Understand system behavior (1 min)
├─ BGAppRefreshTask runs based on USER patterns
│ └─ User rarely opens app = rare runs
└─ BGProcessingTask runs when charging
└─ User doesn't charge overnight = no runs
预期行为
| 任务类型 | 调度行为 |
|---|---|
| BGAppRefreshTask | 在预计的 App 使用时间之前运行 |
| BGProcessingTask | 在充电且设备闲置时运行(通常在夜间) |
| 静默推送 | 受速率限制;14 次推送可能只触发 7 次启动 |
关键要点:你请求的是一个时间窗口。系统决定何时运行(或是否运行)。
症状 6:App 在后台启动时崩溃
系统为执行后台任务而启动 App 时,App 崩溃。
快速诊断(5 分钟)
Crash on background launch?
│
├─ Step 1: Check launch initialization (2 min)
│ ├─ UI setup before task handler?
│ │ └─ Background launch may not have UI context
│ ├─ Accessing files before first unlock?
│ │ └─ Use completeUntilFirstUserAuthentication protection
│ └─ Force unwrapping optionals that may be nil?
│ └─ Guard against nil in background context
│
├─ Step 2: Check handler safety (2 min)
│ ├─ Handler captures self strongly?
│ │ └─ Use [weak self] to prevent retain cycles
│ └─ Handler accesses UI on non-main thread?
│ └─ Dispatch UI work to main queue
│
└─ Step 3: Check data protection (1 min)
└─ Files accessible when device locked?
└─ Use .completeUnlessOpen or .completeUntilFirstUserAuthentication
后台任务的文件保护
// Set appropriate protection when creating files
try data.write(to: url, options: .completeFileProtectionUntilFirstUserAuthentication)
// Or configure in entitlements for entire app
安全的处理程序模式
BGTaskScheduler.shared.register(
forTaskWithIdentifier: "com.app.refresh",
using: nil
) { [weak self] task in
guard let self = self else {
task.setTaskCompleted(success: false)
return
}
// Don't access UI
// Use background-safe APIs only
self.performBackgroundWork(task: task)
}
症状 7:任务多次运行
同一任务似乎反复运行或并行运行。
快速诊断(5 分钟)
Task runs multiple times?
│
├─ Step 1: Check scheduling logic (2 min)
│ ├─ Scheduling on every app launch?
│ │ └─ Check getPendingTaskRequests first
│ ├─ Scheduling in handler AND elsewhere?
│ │ └─ Consolidate to single location
│ └─ Using same identifier for different purposes?
│ └─ Use unique identifiers per task type
│
├─ Step 2: Check for duplicate submissions (2 min)
│ └─ Multiple submit() calls queued?
│ └─ System may batch into single execution
│
└─ Step 3: Check handler execution (1 min)
└─ setTaskCompleted called promptly?
└─ Delay may cause system to think task hung
避免重复调度
func scheduleRefreshIfNeeded() {
BGTaskScheduler.shared.getPendingTaskRequests { requests in
let alreadyScheduled = requests.contains {
$0.identifier == "com.app.refresh"
}
if !alreadyScheduled {
self.scheduleRefresh()
}
}
}
快速诊断检查清单
30 秒检查
- Info.plist 中是否有标识符?
- 是否在 didFinishLaunchingWithOptions 中注册?
- App 是否未被划掉?
5 分钟检查
- 标识符是否完全匹配(区分大小写)?
- 是否已启用后台模式(fetch/processing)?
- 是否在所有路径中调用 setTaskCompleted?
- 是否将过期处理程序设为第一项?
15 分钟排查
- 使用 LLDB 模拟启动是否有效?
- 是否使用 LLDB 模拟并处理了过期?
- 控制台是否显示注册/调度日志?
- 是否使用真实设备(而非仅使用模拟器)?
- 是否使用发布版本(而非仅使用调试版本)?
- 设置中是否已启用后台 App 刷新?
控制台日志筛选器
// All background task events
subsystem:com.apple.backgroundtaskscheduler
// Specific to your app
subsystem:com.apple.backgroundtaskscheduler message:"com.yourapp"
预期日志顺序
- “已注册标识符为……的任务处理程序”
- “正在计划标识符为……的任务”
- “正在启动标识符为……的任务” 4.(执行你的工作)
- “已完成标识符为……的任务”
缺少任何一步,都表示该阶段存在问题。
资源
WWDC:2019-707(调试命令)、2020-10063(7 个因素)
技能:axiom-background-processing、axiom-background-processing-ref
最后更新:2025-12-31
平台:iOS 13+
评论
支持 Markdown