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 のバックグラウンド更新 | 設定で有効になっていますか? |
| システムの予算 | 最近、バックグラウンドで何度も起動していますか? |
| レート制限 | リクエストの頻度が高すぎませんか? |
本番環境でのデバッグ
何が起きているかを追跡するため、ログを追加します。
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 | アプリの使用が予測される時間より前に実行されます |
| BGProcessingTask | 充電中かつアイドル状態のときに実行されます(通常は夜間) |
| サイレントプッシュ | レート制限されます。プッシュ 14 回で起動 7 回となる場合があります |
重要なポイント:時間枠をリクエストするのはアプリですが、実行するかどうか、いつ実行するかはシステムが決定します。
症状 6:バックグラウンド起動時にアプリがクラッシュする
バックグラウンドタスクのためにシステムがアプリを起動すると、アプリがクラッシュします。
クイック診断(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 で登録していますか?
- アプリをスワイプして終了していませんか?
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"
想定されるログの順序
- 「Registered handler for task with identifier」
- 「Scheduling task with identifier」
- 「Starting task with identifier」
- (処理が実行されます)
- 「Task completed with identifier」
いずれかのステップが欠けている場合 = その段階で問題が発生しています。
リソース
WWDC: 2019-707 (デバッグコマンド)、2020-10063 (7 つの要因)
スキル: axiom-background-processing、axiom-background-processing-ref
最終更新: 2025-12-31 プラットフォーム: iOS 13+