axiom-background-processing-diag

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Background Processing Diagnostics

后台处理故障诊断

Symptom-based troubleshooting for background task issues.
Related skills:
axiom-background-processing
(patterns, checklists),
axiom-background-processing-ref
(API reference)

基于症状的后台任务问题排查方案。
相关技能
axiom-background-processing
(模式、检查清单),
axiom-background-processing-ref
(API参考)

Symptom 1: Task Never Runs

症状1:任务从未运行

Handler never called despite successful
submit()
.
调用
submit()
成功,但处理器从未被调用。

Quick Diagnosis (5 minutes)

快速诊断(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
任务从未运行?
├─ 步骤1:检查Info.plist(2分钟)
│  ├─ BGTaskSchedulerPermittedIdentifiers中是否包含完全匹配的标识符?
│  │  └─ 否 → 添加标识符,重新构建
│  ├─ UIBackgroundModes是否包含"fetch"或"processing"?
│  │  └─ 否 → 添加所需模式
│  └─ 标识符与代码是否大小写完全匹配?
│     └─ 否 → 修正拼写错误,重新构建
├─ 步骤2:检查注册时机(2分钟)
│  ├─ 是否在didFinishLaunchingWithOptions中注册?
│  │  └─ 否 → 将注册代码移至return true之前
│  └─ 注册是否在首次调用submit()之前完成?
│     └─ 否 → 确保register()在submit()之前执行
└─ 步骤3:检查应用状态(1分钟)
   ├─ 应用是否从应用切换器中被划走?
   │  └─ 是 → 除非用户重新打开应用,否则无法在后台运行
   └─ 设置中是否禁用了后台应用刷新?
      └─ 是 → 启用该功能或告知用户

Time-Cost Analysis

时间成本分析

ApproachTimeSuccess Rate
Check Info.plist + registration5 min70% (catches most issues)
Add console logging15 min90%
LLDB simulate launch5 min95% (confirms handler works)
Random code changes2+ hoursLow
方法耗时成功率
检查Info.plist + 注册配置5分钟70%(能排查大多数问题)
添加控制台日志15分钟90%
LLDB模拟启动5分钟95%(确认处理器工作正常)
随机修改代码2小时以上

LLDB Quick Test

LLDB快速测试

Verify handler is correctly registered:
lldb
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.

验证处理器是否正确注册:
lldb
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.yourapp.refresh"]
如果触发断点 → 注册正确,问题出在调度/系统因素上。 如果无任何反应 → 注册失败。

Symptom 2: Task Terminates Unexpectedly

症状2:任务意外终止

Handler called but work doesn't complete before termination.
处理器被调用,但工作未完成就终止。

Quick Diagnosis (5 minutes)

快速诊断(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
任务提前终止?
├─ 步骤1:检查过期处理器(1分钟)
│  ├─ 是否在处理器的第一行设置过期处理器?
│  │  └─ 否 → 将其移至第一行
│  └─ 过期处理器是否实际执行了取消工作的逻辑?
│     └─ 否 → 添加取消逻辑
├─ 步骤2:检查setTaskCompleted调用(2分钟)
│  ├─ 成功路径中是否调用了该方法?
│  ├─ 失败路径中是否调用了该方法?
│  ├─ 过期后是否调用了该方法?
│  └─ 任何路径未调用 → 任务从未发出完成信号
├─ 步骤3:检查工作时长(2分钟)
│  ├─ BGAppRefreshTask的工作时长是否超过30秒?
│  │  └─ 是 → 拆分工作或使用BGProcessingTask
│  └─ BGProcessingTask的工作时长是否超过系统限制?
│     └─ 是 → 保存进度,下次启动时恢复

Common Causes

常见原因

CauseFix
Missing expiration handlerSet handler as first line
setTaskCompleted not calledAdd to ALL code paths
Work takes too longChunk and checkpoint
Network timeout > task timeUse background URLSession
Async callback after expirationCheck shouldContinue flag
原因修复方案
缺少过期处理器将处理器设置为第一行代码
未调用setTaskCompleted在所有代码路径中添加调用
工作耗时过长拆分任务并设置检查点
网络超时超过任务时长使用后台URLSession
过期后执行异步回调检查shouldContinue标志

Test Expiration Handling

测试过期处理逻辑

lldb
// 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.

lldb
// 首先模拟启动
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.yourapp.refresh"]

// 然后强制触发过期
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateExpirationForTaskWithIdentifier:@"com.yourapp.refresh"]
验证过期处理器是否运行,且工作是否优雅停止。

Symptom 3: Background URLSession Delegate Not Called

症状3:Background URLSession代理未被调用

Download completes but
didFinishDownloadingTo
never fires.
下载完成,但
didFinishDownloadingTo
从未触发。

Quick Diagnosis (5 minutes)

快速诊断(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?
URLSession代理未被调用?
├─ 步骤1:检查会话配置(2分钟)
│  ├─ 是否使用URLSessionConfiguration.background()?
│  │  └─ 否 → 必须使用后台配置
│  ├─ 会话标识符是否唯一?
│  │  └─ 否 → 使用带Bundle前缀的唯一ID
│  └─ sessionSendsLaunchEvents是否设为true?
│     └─ 否 → 设置为true以在完成时重启应用
├─ 步骤2:检查AppDelegate处理器(2分钟)
│  ├─ 是否实现了handleEventsForBackgroundURLSession?
│  │  └─ 否 → 该方法是处理会话事件的必需方法
│  └─ 是否保存并在后续调用了完成处理器?
│     └─ 否 → 保存处理器,处理完事件后再调用
└─ 步骤3:检查代理赋值(1分钟)
   ├─ 创建会话时是否指定了代理?
   └─ 任务完成时代理是否不为nil?

Required AppDelegate Code

必需的AppDelegate代码

swift
// 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
    }
}

swift
// 保存完成处理器
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
    }
}

Symptom 4: Works in Development, Not Production

症状4:开发环境正常,生产环境异常

Task runs with debugger but fails in release builds or for users.
任务在调试器中运行正常,但在发布版本或用户设备上失败。

Quick Diagnosis (10 minutes)

快速诊断(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
开发环境正常,生产环境异常?
├─ 步骤1:检查系统限制(3分钟)
│  ├─ 是否启用了低电量模式?
│  │  └─ 检查ProcessInfo.isLowPowerModeEnabled
│  ├─ 是否禁用了后台应用刷新?
│  │  └─ 检查UIApplication.backgroundRefreshStatus
│  └─ 电量是否低于20%?
│     └─ 系统会暂停可自由支配的工作
├─ 步骤2:检查应用状态(2分钟)
│  ├─ 应用是否从应用切换器中被强制退出?
│  │  └─ 是 → 除非应用前台启动,否则无法在后台运行
│  └─ 应用是否被用户频繁使用?
│     └─ 很少使用的应用优先级更低
├─ 步骤3:检查构建差异(3分钟)
│  ├─ Debug与Release版本的优化设置是否不同?
│  ├─ 是否有#if DEBUG代码在生产环境中被排除?
│  └─ 发布版本的Bundle标识符是否不同?
└─ 步骤4:添加生产环境日志(2分钟)
   └─ 将任务的调度/启动/完成情况记录到分析工具中

The 7 Scheduling Factors

7大调度影响因素

All affect task execution in production:
FactorCheck
Critically Low BatteryBattery < 20%?
Low Power ModeProcessInfo.isLowPowerModeEnabled
App UsageUser opens app frequently?
App SwitcherApp NOT swiped away?
Background App RefreshSettings enabled?
System BudgetsMany recent background launches?
Rate LimitingRequests too frequent?
所有因素都会影响生产环境中的任务执行:
因素检查项
电量极低电量是否低于20%?
低电量模式ProcessInfo.isLowPowerModeEnabled
应用使用频率用户是否频繁打开应用?
应用切换器状态应用是否未被划走?
后台应用刷新设置中是否启用?
系统预算近期是否有大量后台启动?
速率限制请求是否过于频繁?

Production Debugging

生产环境调试

Add logging to track what's happening:
swift
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)
}

添加日志以跟踪运行情况:
swift
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)
}

Symptom 5: Inconsistent Task Scheduling

症状5:任务调度不一致

Task runs sometimes but not predictably.
任务有时运行,有时不运行,无规律可循。

Quick Diagnosis (5 minutes)

快速诊断(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
调度不一致?
├─ 步骤1:理解earliestBeginDate(2分钟)
│  ├─ 这是最小延迟时间,而非固定调度时间
│  │  └─ 系统会在该时间之后的合适时机运行任务
│  └─ 是否设置为过远的未来时间(超过1周)?
│     └─ 是 → 系统可能会完全跳过该任务
├─ 步骤2:检查调度逻辑(2分钟)
│  ├─ 是否多次调度同一个任务?
│  │  └─ 调用getPendingTaskRequests检查
│  └─ 是否在处理器中调度任务以保持连续性?
│     └─ 是 → 这是实现持续刷新的必需操作
└─ 步骤3:理解系统行为(1分钟)
   ├─ BGAppRefreshTask基于用户使用模式运行
   │  └─ 用户很少打开应用 → 任务运行频率低
   └─ BGProcessingTask在充电时运行
      └─ 用户夜间不充电 → 任务不运行

Expected Behavior

预期行为

Task TypeScheduling Behavior
BGAppRefreshTaskRuns before predicted app usage times
BGProcessingTaskRuns when charging + idle (typically overnight)
Silent PushRate-limited; 14 pushes may = 7 launches
Key insight: You request a time window. System decides when (or if) to run.

任务类型调度行为
BGAppRefreshTask在预测的用户打开应用时间之前运行
BGProcessingTask在充电且设备空闲时运行(通常是夜间)
静默推送受速率限制;14次推送可能仅触发7次启动
核心要点:你请求的是一个时间窗口,系统决定何时(或是否)运行任务。

Symptom 6: App Crashes on Background Launch

症状6:应用在后台启动时崩溃

App crashes when launched by system for background task.
系统为执行后台任务而启动应用时,应用崩溃。

Quick Diagnosis (5 minutes)

快速诊断(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
后台启动时崩溃?
├─ 步骤1:检查启动初始化逻辑(2分钟)
│  ├─ 是否在任务处理器之前进行UI设置?
│  │  └─ 后台启动可能没有UI上下文
│  ├─ 是否在设备首次解锁前访问文件?
│  │  └─ 使用completeUntilFirstUserAuthentication保护级别
│  └─ 是否强制解包可能为nil的可选值?
│     └─ 在后台上下文中要防范nil值
├─ 步骤2:检查处理器安全性(2分钟)
│  ├─ 处理器是否强引用self?
│  │  └─ 使用[weak self]避免循环引用
│  └─ 处理器是否在非主线程访问UI?
│     └─ 将UI工作调度到主队列
└─ 步骤3:检查数据保护(1分钟)
   └─ 设备锁定时文件是否可访问?
      └─ 使用.completeUnlessOpen或.completeUntilFirstUserAuthentication

File Protection for Background Tasks

后台任务的文件保护设置

swift
// Set appropriate protection when creating files
try data.write(to: url, options: .completeFileProtectionUntilFirstUserAuthentication)

// Or configure in entitlements for entire app
swift
// 创建文件时设置合适的保护级别
try data.write(to: url, options: .completeFileProtectionUntilFirstUserAuthentication)

// 或在 entitlements 中为整个应用配置

Safe Handler Pattern

安全的处理器模式

swift
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)
}

swift
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)
}

Symptom 7: Task Runs Multiple Times

症状7:任务多次运行

Same task appears to run repeatedly or in parallel.
同一个任务似乎重复运行或并行运行。

Quick Diagnosis (5 minutes)

快速诊断(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
任务多次运行?
├─ 步骤1:检查调度逻辑(2分钟)
│  ├─ 是否每次应用启动都调度任务?
│  │  └─ 先调用getPendingTaskRequests检查
│  ├─ 是否同时在处理器和其他地方调度任务?
│  │  └─ 统一到单一位置调度
│  └─ 是否将同一标识符用于不同用途?
│     └─ 为不同类型的任务使用唯一标识符
├─ 步骤2:检查重复提交(2分钟)
│  └─ 是否有多个submit()调用被排队?
│     └─ 系统可能会将其合并为单次执行
└─ 步骤3:检查处理器执行情况(1分钟)
   └─ 是否及时调用了setTaskCompleted?
      └─ 延迟调用可能导致系统认为任务挂起

Prevent Duplicate Scheduling

防止重复调度

swift
func scheduleRefreshIfNeeded() {
    BGTaskScheduler.shared.getPendingTaskRequests { requests in
        let alreadyScheduled = requests.contains {
            $0.identifier == "com.app.refresh"
        }

        if !alreadyScheduled {
            self.scheduleRefresh()
        }
    }
}

swift
func scheduleRefreshIfNeeded() {
    BGTaskScheduler.shared.getPendingTaskRequests { requests in
        let alreadyScheduled = requests.contains {
            $0.identifier == "com.app.refresh"
        }

        if !alreadyScheduled {
            self.scheduleRefresh()
        }
    }
}

Quick Diagnostic Checklist

快速诊断检查清单

30-Second Check

30秒快速检查

  • Info.plist has identifier?
  • Registration in didFinishLaunchingWithOptions?
  • App not swiped away?
  • Info.plist中是否包含标识符?
  • 是否在didFinishLaunchingWithOptions中注册?
  • 应用是否未被划走?

5-Minute Check

5分钟检查

  • Identifiers exactly match (case-sensitive)?
  • Background mode enabled (fetch/processing)?
  • setTaskCompleted called in all paths?
  • Expiration handler set first?
  • 标识符是否完全匹配(区分大小写)?
  • 是否启用了后台模式(fetch/processing)?
  • 是否在所有路径中调用了setTaskCompleted?
  • 是否在第一行设置了过期处理器?

15-Minute Investigation

15分钟深入排查

  • LLDB simulate launch works?
  • LLDB simulate expiration handled?
  • Console shows registration/scheduling logs?
  • Real device (not just simulator)?
  • Release build (not just debug)?
  • Background App Refresh enabled in Settings?

  • LLDB模拟启动是否正常?
  • LLDB模拟过期是否能正确处理?
  • 控制台是否显示注册/调度日志?
  • 是否在真实设备上测试(而非仅模拟器)?
  • 是否测试了发布版本(而非仅Debug版本)?
  • 设置中是否启用了后台应用刷新?

Console Log Filters

控制台日志过滤器

// All background task events
subsystem:com.apple.backgroundtaskscheduler

// Specific to your app
subsystem:com.apple.backgroundtaskscheduler message:"com.yourapp"
// 所有后台任务事件
subsystem:com.apple.backgroundtaskscheduler

// 仅针对你的应用
subsystem:com.apple.backgroundtaskscheduler message:"com.yourapp"

Expected Log Sequence

预期日志序列

  1. "Registered handler for task with identifier"
  2. "Scheduling task with identifier"
  3. "Starting task with identifier"
  4. (your work executes)
  5. "Task completed with identifier"
Missing any step = issue at that stage.

  1. "Registered handler for task with identifier"
  2. "Scheduling task with identifier"
  3. "Starting task with identifier"
  4. (你的工作执行)
  5. "Task completed with identifier"
缺少任何步骤 → 该阶段存在问题。

Resources

参考资源

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+
WWDC:2019-707(调试命令),2020-10063(7大影响因素)
技能:axiom-background-processing, axiom-background-processing-ref

最后更新:2025-12-31 支持平台:iOS 13+