axiom-background-processing-diag by charleswiltgen/axiom
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-background-processing-diag基于症状的后台任务问题排查。
相关技能 : axiom-background-processing (模式、检查清单), axiom-background-processing-ref (API 参考)
尽管 submit() 成功,但处理程序从未被调用。
任务从未运行?
│
├─ 步骤 1:检查 Info.plist (2 分钟)
│ ├─ BGTaskSchedulerPermittedIdentifiers 是否包含 EXACT 标识符?
│ │ └─ 否 → 添加标识符,重新构建
│ ├─ UIBackgroundModes 是否包含 "fetch" 或 "processing"?
│ │ └─ 否 → 添加所需模式
│ └─ 标识符是否与代码大小写敏感匹配?
│ └─ 否 → 修正拼写错误,重新构建
│
├─ 步骤 2:检查注册时机 (2 分钟)
│ ├─ 是否在 didFinishLaunchingWithOptions 中注册?
│ │ └─ 否 → 将注册移到 return true 之前
│ └─ 注册是否在首次 submit() 之前?
│ └─ 否 → 确保 register() 在 submit() 之前
│
└─ 步骤 3:检查应用状态 (1 分钟)
├─ 应用是否从应用切换器中被划掉关闭?
│ └─ 是 → 在用户打开应用前无法后台运行
└─ 设置中是否禁用了后台应用刷新?
└─ 是 → 启用或通知用户
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
| 方法 | 时间 | 成功率 |
|---|
| 检查 Info.plist + 注册 | 5 分钟 | 70% (捕获大多数问题) |
| 添加控制台日志 | 15 分钟 | 90% |
| LLDB 模拟启动 | 5 分钟 | 95% (确认处理程序有效) |
| 随机代码更改 | 2+ 小时 | 低 |
验证处理程序是否正确注册:
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.yourapp.refresh"]
如果断点命中 → 注册正确,问题是调度/系统因素。如果无反应 → 注册损坏。
处理程序被调用,但工作未在终止前完成。
任务提前终止?
│
├─ 步骤 1:检查过期处理程序 (1 分钟)
│ ├─ 过期处理程序是否在处理程序中 FIRST 设置?
│ │ └─ 否 → 移到第一行
│ └─ 过期处理程序是否实际取消了工作?
│ └─ 否 → 添加取消逻辑
│
├─ 步骤 2:检查 setTaskCompleted (2 分钟)
│ ├─ 是否在成功路径中调用?
│ ├─ 是否在失败路径中调用?
│ ├─ 是否在过期后调用?
│ └─ 任何路径缺失 → 任务从未发出完成信号
│
├─ 步骤 3:检查工作持续时间 (2 分钟)
│ ├─ BGAppRefreshTask 工作 > 30 秒?
│ │ └─ 是 → 分块工作或使用 BGProcessingTask
│ └─ BGProcessingTask 工作 > 系统限制?
│ └─ 是 → 保存进度,下次启动时恢复
| 原因 | 修复 |
|---|---|
| 缺少过期处理程序 | 将处理程序设为第一行 |
| 未调用 setTaskCompleted | 添加到所有代码路径 |
| 工作时间过长 | 分块和检查点 |
| 网络超时 > 任务时间 | 使用后台 URLSession |
| 过期后的异步回调 | 检查 shouldContinue 标志 |
// 首先模拟启动
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.yourapp.refresh"]
// 然后强制过期
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateExpirationForTaskWithIdentifier:@"com.yourapp.refresh"]
验证过期处理程序运行且工作优雅停止。
下载完成但 didFinishDownloadingTo 从未触发。
URLSession 委托未被调用?
│
├─ 步骤 1:检查会话配置 (2 分钟)
│ ├─ 是否使用 URLSessionConfiguration.background()?
│ │ └─ 否 → 必须使用后台配置
│ ├─ 会话标识符是否唯一?
│ │ └─ 否 → 使用唯一的包前缀 ID
│ └─ sessionSendsLaunchEvents = true?
│ └─ 否 → 设置为在完成时重新启动应用
│
├─ 步骤 2:检查 AppDelegate 处理程序 (2 分钟)
│ ├─ 是否实现了 handleEventsForBackgroundURLSession?
│ │ └─ 否 → 需要处理会话事件
│ └─ 完成处理程序是否存储并在稍后调用?
│ └─ 否 → 存储处理程序,事件处理后调用
│
└─ 步骤 3:检查委托分配 (1 分钟)
├─ 会话是否使用委托创建?
└─ 任务完成时委托不为 nil?
// 存储完成处理程序
var backgroundSessionCompletionHandler: (() -> Void)?
func application(_ application: UIApplication,
handleEventsForBackgroundURLSession identifier: String,
completionHandler: @escaping () -> Void) {
backgroundSessionCompletionHandler = completionHandler
}
// 在所有事件处理后调用
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
DispatchQueue.main.async {
self.backgroundSessionCompletionHandler?()
self.backgroundSessionCompletionHandler = nil
}
}
任务在调试器中运行,但在发布版本或用户设备上失败。
开发正常,生产失败?
│
├─ 步骤 1:检查系统约束 (3 分钟)
│ ├─ 是否启用了低电量模式?
│ │ └─ 检查 ProcessInfo.isLowPowerModeEnabled
│ ├─ 是否禁用了后台应用刷新?
│ │ └─ 检查 UIApplication.backgroundRefreshStatus
│ └─ 电量 < 20%?
│ └─ 系统暂停可自由支配的工作
│
├─ 步骤 2:检查应用状态 (2 分钟)
│ ├─ 应用是否从应用切换器中强制退出?
│ │ └─ 是 → 在前台启动前无法后台运行
│ └─ 应用最近是否被使用?
│ └─ 很少使用的应用优先级较低
│
├─ 步骤 3:检查构建差异 (3 分钟)
│ ├─ Debug 与 Release 优化差异?
│ ├─ #if DEBUG 代码排除了生产环境?
│ └─ 发布版本中是否使用了不同的包标识符?
│
└─ 步骤 4:添加生产日志 (2 分钟)
└─ 将任务调度/启动/完成记录到分析工具
所有这些都会影响生产环境中的任务执行:
| 因素 | 检查 |
|---|---|
| 电量严重不足 | 电量 < 20%? |
| 低电量模式 | ProcessInfo.isLowPowerModeEnabled |
| 应用使用情况 | 用户是否频繁打开应用? |
| 应用切换器 | 应用未被划掉关闭? |
| 后台应用刷新 | 设置中是否启用? |
| 系统预算 | 最近是否有很多后台启动? |
| 速率限制 | 请求是否过于频繁? |
添加日志以跟踪发生的情况:
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")
// ... 工作 ...
Analytics.log("background_task_completed")
task.setTaskCompleted(success: true)
}
任务有时运行,但不可预测。
调度不一致?
│
├─ 步骤 1:理解 earliestBeginDate (2 分钟)
│ ├─ 这是最小延迟,而非计划时间
│ │ └─ 系统在此日期之后方便时运行
│ └─ 是否设置得太远 (> 1 周)?
│ └─ 系统可能完全跳过任务
│
├─ 步骤 2:检查调度模式 (2 分钟)
│ ├─ 是否多次调度同一任务?
│ │ └─ 调用 getPendingTaskRequests 检查
│ └─ 是否在处理程序中调度以实现连续性?
│ └─ 连续刷新所必需
│
└─ 步骤 3:理解系统行为 (1 分钟)
├─ BGAppRefreshTask 基于用户模式运行
│ └─ 用户很少打开应用 = 很少运行
└─ BGProcessingTask 在充电时运行
└─ 用户夜间不充电 = 不运行
| 任务类型 | 调度行为 |
|---|---|
| BGAppRefreshTask | 在预测的应用使用时间之前运行 |
| BGProcessingTask | 在充电 + 空闲时运行 (通常在夜间) |
| 静默推送 | 速率受限;14 次推送可能 = 7 次启动 |
关键见解 : 你请求一个时间窗口。系统决定何时 (或是否) 运行。
应用在系统为后台任务启动时崩溃。
后台启动时崩溃?
│
├─ 步骤 1:检查启动初始化 (2 分钟)
│ ├─ UI 设置是否在任务处理程序之前?
│ │ └─ 后台启动可能没有 UI 上下文
│ ├─ 是否在首次解锁前访问文件?
│ │ └─ 使用 completeUntilFirstUserAuthentication 保护
│ └─ 是否强制解包可能为 nil 的可选值?
│ └─ 在后台上下文中防范 nil
│
├─ 步骤 2:检查处理程序安全性 (2 分钟)
│ ├─ 处理程序是否强捕获 self?
│ │ └─ 使用 [weak self] 防止循环引用
│ └─ 处理程序是否在非主线程访问 UI?
│ └─ 将 UI 工作分派到主队列
│
└─ 步骤 3:检查数据保护 (1 分钟)
└─ 设备锁定时文件是否可访问?
└─ 使用 .completeUnlessOpen 或 .completeUntilFirstUserAuthentication
// 创建文件时设置适当的保护
try data.write(to: url, options: .completeFileProtectionUntilFirstUserAuthentication)
// 或在权利中为整个应用配置
BGTaskScheduler.shared.register(
forTaskWithIdentifier: "com.app.refresh",
using: nil
) { [weak self] task in
guard let self = self else {
task.setTaskCompleted(success: false)
return
}
// 不要访问 UI
// 仅使用后台安全的 API
self.performBackgroundWork(task: task)
}
同一任务似乎重复运行或并行运行。
任务多次运行?
│
├─ 步骤 1:检查调度逻辑 (2 分钟)
│ ├─ 是否在每次应用启动时调度?
│ │ └─ 首先检查 getPendingTaskRequests
│ ├─ 是否在处理程序中和其他地方调度?
│ │ └─ 合并到单一位置
│ └─ 是否对不同的目的使用相同的标识符?
│ └─ 每种任务类型使用唯一的标识符
│
├─ 步骤 2:检查重复提交 (2 分钟)
│ └─ 是否排队了多个 submit() 调用?
│ └─ 系统可能批处理为单次执行
│
└─ 步骤 3:检查处理程序执行 (1 分钟)
└─ setTaskCompleted 是否被及时调用?
└─ 延迟可能导致系统认为任务挂起
func scheduleRefreshIfNeeded() {
BGTaskScheduler.shared.getPendingTaskRequests { requests in
let alreadyScheduled = requests.contains {
$0.identifier == "com.app.refresh"
}
if !alreadyScheduled {
self.scheduleRefresh()
}
}
}
// 所有后台任务事件
subsystem:com.apple.backgroundtaskscheduler
// 特定于你的应用
subsystem:com.apple.backgroundtaskscheduler message:"com.yourapp"
缺少任何步骤 = 该阶段的问题。
WWDC : 2019-707 (调试命令), 2020-10063 (7 个因素)
技能 : axiom-background-processing, axiom-background-processing-ref
最后更新 : 2025-12-31 平台 : iOS 13+
每周安装量
89
仓库
GitHub 星标数
590
首次出现
Jan 21, 2026
安全审计
安装于
opencode75
claude-code70
codex69
gemini-cli68
cursor67
github-copilot65
Symptom-based troubleshooting for background task issues.
Related skills : axiom-background-processing (patterns, checklists), axiom-background-processing-ref (API reference)
Handler never called despite successful submit().
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
| Approach | Time | Success Rate |
|---|---|---|
| Check Info.plist + registration | 5 min | 70% (catches most issues) |
| Add console logging | 15 min | 90% |
| LLDB simulate launch | 5 min | 95% (confirms handler works) |
| Random code changes | 2+ hours | Low |
Verify handler is correctly registered:
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.yourapp.refresh"]
If breakpoint hits → Registration correct, issue is scheduling/system factors. If nothing happens → Registration broken.
Handler called but work doesn't complete before termination.
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
| Cause | Fix |
|---|---|
| Missing expiration handler | Set handler as first line |
| setTaskCompleted not called | Add to ALL code paths |
| Work takes too long | Chunk and checkpoint |
| Network timeout > task time | Use background URLSession |
| Async callback after expiration | Check shouldContinue flag |
// 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"]
Verify expiration handler runs and work stops gracefully.
Download completes but didFinishDownloadingTo never fires.
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?
// 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
}
}
Task runs with debugger but fails in release builds or for users.
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
All affect task execution in production:
| Factor | Check |
|---|---|
| Critically Low Battery | Battery < 20%? |
| Low Power Mode | ProcessInfo.isLowPowerModeEnabled |
| App Usage | User opens app frequently? |
| App Switcher | App NOT swiped away? |
| Background App Refresh | Settings enabled? |
| System Budgets | Many recent background launches? |
| Rate Limiting | Requests too frequent? |
Add logging to track what's happening:
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)
}
Task runs sometimes but not predictably.
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
| Task Type | Scheduling Behavior |
|---|---|
| BGAppRefreshTask | Runs before predicted app usage times |
| BGProcessingTask | Runs when charging + idle (typically overnight) |
| Silent Push | Rate-limited; 14 pushes may = 7 launches |
Key insight : You request a time window. System decides when (or if) to run.
App crashes when launched by system for background task.
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)
}
Same task appears to run repeatedly or in parallel.
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()
}
}
}
// All background task events
subsystem:com.apple.backgroundtaskscheduler
// Specific to your app
subsystem:com.apple.backgroundtaskscheduler message:"com.yourapp"
Missing any step = issue at that stage.
WWDC : 2019-707 (debugging commands), 2020-10063 (7 factors)
Skills : axiom-background-processing, axiom-background-processing-ref
Last Updated : 2025-12-31 Platforms : iOS 13+
Weekly Installs
89
Repository
GitHub Stars
590
First Seen
Jan 21, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode75
claude-code70
codex69
gemini-cli68
cursor67
github-copilot65
Datadog自动化监控:通过Rube MCP与Composio实现指标、日志、仪表板管理
69 周安装
Intercom自动化指南:通过Rube MCP与Composio实现客户支持对话管理
69 周安装
二进制初步分析指南:使用ReVa工具快速识别恶意软件与逆向工程
69 周安装
PrivateInvestigator 道德人员查找工具 | 公开数据调查、反向搜索与背景研究
69 周安装
TorchTitan:PyTorch原生分布式大语言模型预训练平台,支持4D并行与H100 GPU加速
69 周安装
screenshot 截图技能:跨平台桌面截图工具,支持macOS/Linux权限管理与多模式捕获
69 周安装