Background Processing Diagnostics
Symptom-based troubleshooting for background task issues.
Related skills: axiom-background-processing (patterns, checklists), axiom-background-processing-ref (API reference)
Symptom 1: Task Never Runs
Handler never called despite successful submit().
Quick Diagnosis (5 minutes)
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
Time-Cost Analysis
| 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 |
LLDB Quick Test
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.
Symptom 2: Task Terminates Unexpectedly
Handler called but work doesn't complete before termination.
Quick Diagnosis (5 minutes)
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
Common Causes
| 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 |
Test Expiration Handling
// 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.
Symptom 3: Background URLSession Delegate Not Called
Download completes but didFinishDownloadingTo never fires.
Quick Diagnosis (5 minutes)
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?
Required AppDelegate Code
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
Task runs with debugger but fails in release builds or for users.
Quick Diagnosis (10 minutes)
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
The 7 Scheduling Factors
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? |
Production Debugging
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")
Analytics.log("background_task_completed")
task.setTaskCompleted(success: true)
}
Symptom 5: Inconsistent Task Scheduling
Task runs sometimes but not predictably.
Quick Diagnosis (5 minutes)
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
Expected Behavior
| 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.
Symptom 6: App Crashes on Background Launch
App crashes when launched by system for background task.
Quick Diagnosis (5 minutes)
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
File Protection for Background Tasks
try data.write(to: url, options: .completeFileProtectionUntilFirstUserAuthentication)
Safe Handler Pattern
BGTaskScheduler.shared.register(
forTaskWithIdentifier: "com.app.refresh",
using: nil
) { [weak self] task in
guard let self = self else {
task.setTaskCompleted(success: false)
return
}
self.performBackgroundWork(task: task)
}
Symptom 7: Task Runs Multiple Times
Same task appears to run repeatedly or in parallel.
Quick Diagnosis (5 minutes)
Task runs multiple times?
β
ββ Step 1: Check scheduling logic (2 min)
β ββ Scheduling on every app launch?
β β ββ C