axiom-background-processing-ref について
iOS 開発者向けのバックグラウンド実行 API リファレンスです。タスクスケジューリング、バックグラウンド URLSession、プッシュ通知、SwiftUI 連携を扱い、設定や実行制約、テスト方法を説明します。
主な機能
- 01更新タスクと処理タスクの登録・スケジュール
- 02ユーザー開始の継続処理への対応
- 03バックグラウンド URLSession による転送
- 04バックグラウンド移行後の短時間の重要処理
- 05タスク起動、期限切れ、保留リクエストのテスト
- 06システムのスケジュール制約とサイレントプッシュの確認
活用シーン
- 01コンテンツ更新や延期可能なメンテナンスの予約
- 02アプリのバックグラウンド移行後もユーザー開始の書き出しを継続
- 03アプリ終了後も大容量のダウンロードやアップロードを実行
- 04タスクのスケジュールや期限切れ処理をデバッグ
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ charleswiltgen/ axiom --skill axiom-background-processing-refインストール後、Claude Code で/axiom-background-processing-refを入力して読み込まれたか確認します。
詳細
バックグラウンド処理リファレンス
WWDC セッションのコード例を含む、iOS バックグラウンド実行の完全な API リファレンスです。
関連スキル:axiom-background-processing(判断ツリー、パターン)、axiom-background-processing-diag(トラブルシューティング)
パート 1:BGTaskScheduler の登録
Info.plist の設定
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.yourapp.refresh</string>
<string>com.yourapp.maintenance</string>
<string>com.yourapp.export.*</string>
</array>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>processing</string>
</array>
ハンドラーの登録
import BackgroundTasks
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Register BEFORE returning from didFinishLaunching
BGTaskScheduler.shared.register(
forTaskWithIdentifier: "com.yourapp.refresh",
using: nil // nil = system creates serial background queue
) { task in
self.handleAppRefresh(task: task as! BGAppRefreshTask)
}
BGTaskScheduler.shared.register(
forTaskWithIdentifier: "com.yourapp.maintenance",
using: nil
) { task in
self.handleMaintenance(task: task as! BGProcessingTask)
}
return true
}
パラメーター:
forTaskWithIdentifier:Info.plist と完全に一致する必要があります(大文字と小文字を区別)using:ハンドラーのコールバック用 DispatchQueue。nil の場合、システムが作成しますlaunchHandler:タスクの起動時に呼び出されます。BGTask のサブクラスを受け取ります
登録のタイミング
WWDC 2019-707 より:
「アプリケーションの起動が完了する前に、起動ハンドラーを登録します」
次の場所で登録します:
- ✅
application(_:didFinishLaunchingWithOptions:)内でreturn trueより前 - ❌ viewDidLoad、ボタンハンドラー、非同期コールバック内では登録しないでください
パート 2:BGAppRefreshTask
目的
1 日を通してアプリのコンテンツを最新の状態に保ちます。システムはユーザーの使用パターンに基づいてアプリを起動します。
実行時間
約 30 秒(従来のバックグラウンドフェッチと同じ)
スケジュール設定
func scheduleAppRefresh() {
let request = BGAppRefreshTaskRequest(identifier: "com.yourapp.refresh")
// earliestBeginDate = MINIMUM delay (not exact time)
// System decides actual time based on usage patterns
request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
do {
try BGTaskScheduler.shared.submit(request)
} catch BGTaskScheduler.Error.notPermitted {
// Background App Refresh disabled in Settings
} catch BGTaskScheduler.Error.tooManyPendingTaskRequests {
// Too many pending requests for this identifier
} catch BGTaskScheduler.Error.unavailable {
// Background tasks not available (Simulator, etc.)
} catch {
print("Schedule failed: \(error)")
}
}
// Schedule when app enters background
func applicationDidEnterBackground(_ application: UIApplication) {
scheduleAppRefresh()
}
ハンドラー
func handleAppRefresh(task: BGAppRefreshTask) {
// 1. Set expiration handler FIRST
task.expirationHandler = { [weak self] in
self?.currentOperation?.cancel()
}
// 2. Schedule NEXT refresh (continuous pattern)
scheduleAppRefresh()
// 3. Perform work
let operation = fetchLatestContentOperation()
currentOperation = operation
operation.completionBlock = {
// 4. Signal completion (REQUIRED)
task.setTaskCompleted(success: !operation.isCancelled)
}
operationQueue.addOperation(operation)
}
BGAppRefreshTaskRequest のプロパティ
| プロパティ | 型 | 説明 |
|---|---|---|
identifier | String | Info.plist と一致する必要があります |
earliestBeginDate | Date? | 実行までの最小待機時間 |
パート 3:BGProcessingTask
目的
延期可能なメンテナンス作業(データベースのクリーンアップ、ML のトレーニング、バックアップ)を行います。システムに負荷をかけにくい時間帯、通常は充電中の夜間に実行されます。
実行時間
数分(リフレッシュタスクより大幅に長い時間)
制約付きのスケジュール設定
func scheduleMaintenanceIfNeeded() {
// Only schedule if work is needed
guard Date().timeIntervalSince(lastMaintenanceDate) > 7 * 24 * 3600 else {
return
}
let request = BGProcessingTaskRequest(identifier: "com.yourapp.maintenance")
// CRITICAL for CPU-intensive work
request.requiresExternalPower = true
// Optional: Need network for cloud sync
request.requiresNetworkConnectivity = true
// Keep within 1 week — longer may be skipped
// request.earliestBeginDate = ...
do {
try BGTaskScheduler.shared.submit(request)
} catch {
print("Schedule failed: \(error)")
}
}
進捗チェックポイント機能付きハンドラー
func handleMaintenance(task: BGProcessingTask) {
var shouldContinue = true
task.expirationHandler = {
shouldContinue = false
}
Task {
for item in workItems {
guard shouldContinue else {
// Expiration called — save progress and exit
saveProgress()
break
}
await processItem(item)
saveProgress() // Checkpoint after each item
}
task.setTaskCompleted(success: shouldContinue)
}
}
BGProcessingTaskRequest のプロパティ
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
identifier | String | — | Info.plist と一致する必要があります |
earliestBeginDate | Date? | nil | 最小待機時間 |
requiresNetworkConnectivity | Bool | false | ネットワーク接続を待ちます |
requiresExternalPower | Bool | false | 充電を待ちます |
CPU Monitor の無効化
「これまでにないことですが、処理タスクの実行中はこれを無効にできる機能を提供します。これにより、デバイスを電源に接続している間、ハードウェアを最大限に活用できます」
requiresExternalPower = true の場合、通常は CPU 負荷の高いバックグラウンドアプリを終了させる CPU Monitor が無効になります。
パート 4:BGContinuedProcessingTask(iOS 26 以降)
目的
アプリがバックグラウンドに移行した後も、ユーザーが開始した作業を継続し、進捗をシステム UI に表示します。WWDC 2025-227 より。
対象外:自動タスク、メンテナンス、同期。ユーザーが明示的に開始する必要があります。
使用例
- 写真や動画の書き出し
- コンテンツの公開
- 接続済みアクセサリの更新
- ファイルの圧縮
Info.plist(ワイルドカードパターン)
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.yourapp.export.*</string>
</array>
動的な登録
他のタスクとは異なり、ユーザーが操作を開始した時点で登録します:
func userTappedExportButton() {
// Register dynamically
BGTaskScheduler.shared.register(
forTaskWithIdentifier: "com.yourapp.export.photos"
) { task in
let continuedTask = task as! BGContinuedProcessingTask
self.handleExport(task: continuedTask)
}
// Submit immediately
submitExportRequest()
}
進捗 UI を表示するサブミッション
func submitExportRequest() {
let request = BGContinuedProcessingTaskRequest(
identifier: "com.yourapp.export.photos",
title: "Exporting Photos", // Shown in system UI
subtitle: "0 of 100 photos complete" // Shown in system UI
)
// Strategy: .fail = reject if can't start now; .enqueue = queue (default)
request.strategy = .fail
do {
try BGTaskScheduler.shared.submit(request)
} catch {
// Show error — can't run in background now
showError("Cannot export in background right now")
}
}
進捗の報告が必須のハンドラー
func handleExport(task: BGContinuedProcessingTask) {
var shouldContinue = true
task.expirationHandler = {
shouldContinue = false
}
// MANDATORY: Report progress
// Tasks with no progress updates are AUTO-EXPIRED
task.progress.totalUnitCount = Int64(photos.count)
task.progress.completedUnitCount = 0
Task {
for (index, photo) in photos.enumerated() {
guard shouldContinue else { break }
await exportPhoto(photo)
// Update progress — system displays to user
task.progress.completedUnitCount = Int64(index + 1)
}
task.setTaskCompleted(success: shouldContinue)
}
}
BGContinuedProcessingTaskRequest のプロパティ
| プロパティ | 型 | 説明 |
|---|---|---|
identifier | String | ワイルドカードを使用すると、動的なサフィックスを付けられます |
title | String | システムの進捗 UI に表示されます |
subtitle | String | システムの進捗 UI に表示されます |
strategy | Strategy | .fail または .enqueue(デフォルト) |
ストラテジーのオプション
// .fail — Reject if can't start immediately
request.strategy = .fail
// .enqueue — Queue if can't start (default)
// Task may run later
GPU アクセス(iOS 26 以降)
// Check if GPU available for background task
let supportedResources = BGTaskScheduler.shared.supportedResources
if supportedResources.contains(.gpu) {
// GPU is available
}
パート 5:beginBackgroundTask
目的
アプリがバックグラウンドに移行するとき、重要な作業を完了させます(約 30 秒)。状態の保存やアップロードの完了に使用します。
基本パターン
var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid
func applicationDidEnterBackground(_ application: UIApplication) {
backgroundTaskID = application.beginBackgroundTask(withName: "Save State") {
// Expiration handler — clean up immediately
self.saveProgress()
application.endBackgroundTask(self.backgroundTaskID)
self.backgroundTaskID = .invalid
}
// Do critical work
saveEssentialState { [weak self] in
guard let self = self,
self.backgroundTaskID != .invalid else { return }
// End task AS SOON AS work completes
UIApplication.shared.endBackgroundTask(self.backgroundTaskID)
self.backgroundTaskID = .invalid
}
}
要点
- 完了したらすぐに
endBackgroundTaskを呼び出してください。有効期限が切れるまで待たないでください - 終了しないと、システムによってアプリが終了される可能性があります
- 最大約 30 秒ですが、保証されるものではありません
- 継続的な作業ではなく、最終処理に使用してください
SwiftUI/SceneDelegate
.onChange(of: scenePhase) { newPhase in
if newPhase == .background {
startBackgroundTask()
}
}
パート 6:バックグラウンド URLSession
目的
アプリの終了後も継続する大容量のダウンロード/アップロード。システムデーモンに処理を引き継ぎます。
設定
lazy var backgroundSession: URLSession = {
let config = URLSessionConfiguration.background(
withIdentifier: "com.yourapp.downloads"
)
// App relaunched when task completes
config.sessionSendsLaunchEvents = true
// System chooses optimal time (WiFi, charging)
config.isDiscretionary = true
// Timeout for requests (not the download itself)
config.timeoutIntervalForRequest = 60
return URLSession(configuration: config, delegate: self, delegateQueue: nil)
}()
ダウンロードの開始
func downloadFile(from url: URL) {
let task = backgroundSession.downloadTask(with: url)
task.resume()
// Work continues even if app terminates
}
AppDelegate ハンドラー
var backgroundSessionCompletionHandler: (() -> Void)?
func application(
_ application: UIApplication,
handleEventsForBackgroundURLSession identifier: String,
completionHandler: @escaping () -> Void
) {
// Store — call after all events processed
backgroundSessionCompletionHandler = completionHandler
}
URLSessionDelegate の実装
extension AppDelegate: URLSessionDelegate, URLSessionDownloadDelegate {
func urlSession(
_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didFinishDownloadingTo location: URL
) {
// MUST move file immediately — temp location deleted after return
let destination = getDestinationURL(for: downloadTask)
try? FileManager.default.moveItem(at: location, to: destination)
}
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
// All events processed — call stored completion handler
DispatchQueue.main.async {
self.backgroundSessionCompletionHandler?()
self.backgroundSessionCompletionHandler = nil
}
}
}
設定プロパティ
| プロパティ | デフォルト | 説明 |
|---|---|---|
sessionSendsLaunchEvents | false | 完了時にアプリを再起動します |
isDiscretionary | false | 最適な条件が整うまで待機します |
allowsCellularAccess | true | モバイルネットワークを許可します |
allowsExpensiveNetworkAccess | true | 従量制ネットワークを許可します |
allowsConstrainedNetworkAccess | true | 低データモードを許可します |
パート 7:バックグラウンドタスクのテスト
LLDB デバッグコマンド
デバッガーでアプリを一時停止してから、次を実行します。
// Trigger task launch
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.yourapp.refresh"]
// Trigger task expiration
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateExpirationForTaskWithIdentifier:@"com.yourapp.refresh"]
テストのワークフロー
- タスクハンドラーにブレークポイントを設定します
- アプリを実行し、バックグラウンドに移行させます
- 実行を一時停止します(Debug → Pause)
- 起動シミュレーションコマンドを実行します
- 再開します。ブレークポイントに到達するはずです
- expiration のシミュレーションで有効期限切れ時の処理をテストします
コンソールログ
Console.app で次の条件を指定します。
subsystem:com.apple.backgroundtaskscheduler
getPendingTaskRequests
スケジュール済みの内容を確認します。
BGTaskScheduler.shared.getPendingTaskRequests { requests in
for request in requests {
print("Pending: \(request.identifier)")
print(" Earliest: \(request.earliestBeginDate ?? Date())")
}
}
パート 8:スロットリングとシステムの制約
スケジューリングに影響する 7 つの要因
| 要因 | 確認方法 | 影響 |
|---|---|---|
| バッテリー残量が極めて少ない | バッテリー < 約 20% | 任意の処理が一時停止されます |
| 低電力モード | ProcessInfo.isLowPowerModeEnabled | アクティビティが制限されます |
| アプリの使用状況 | ユーザーがアプリを頻繁に開いているか | 優先度が上がります |
| App Switcher | スワイプして閉じていないか | スワイプして閉じるとバックグラウンド処理は行われません |
| App のバックグラウンド更新 | backgroundRefreshStatus | オフの場合、BGAppRefresh は実行されません |
| システムの予算 | 最近、多数の起動があったか | 予算は消費され、毎日補充されます |
| レート制限 | リクエストの頻度が高すぎないか | システムが起動間隔を空けます |
制約の確認
// Low Power Mode
if ProcessInfo.processInfo.isLowPowerModeEnabled {
// Reduce background work
}
// Listen for changes
NotificationCenter.default.publisher(for: .NSProcessInfoPowerStateDidChange)
.sink { _ in
// Adapt behavior
}
// Background App Refresh status
switch UIApplication.shared.backgroundRefreshStatus {
case .available:
// Can schedule tasks
case .denied:
// User disabled — prompt in Settings
case .restricted:
// MDM or parental controls — cannot enable
@unknown default:
break
}
サーマル状態
switch ProcessInfo.processInfo.thermalState {
case .nominal:
break // Normal operation
case .fair:
// Reduce intensive work
case .serious:
// Minimize all background activity
case .critical:
// Stop non-essential work immediately
@unknown default:
break
}
NotificationCenter.default.publisher(for: ProcessInfo.thermalStateDidChangeNotification)
.sink { _ in
// Respond to thermal changes
}
パート 9:バックグラウンド処理用プッシュ通知
サイレントプッシュのペイロード
{
"aps": {
"content-available": 1
},
"custom-data": "your-payload"
}
APNS の優先度
apns-priority: 5 // Discretionary — energy efficient (recommended)
apns-priority: 10 // Immediate — only for time-sensitive
ハンドラー
func application(
_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
) {
guard userInfo["aps"] as? [String: Any] != nil else {
completionHandler(.noData)
return
}
Task {
do {
let hasNewData = try await fetchLatestData()
completionHandler(hasNewData ? .newData : .noData)
} catch {
completionHandler(.failed)
}
}
}
レート制限の動作
「一定の時間内に 14 件のプッシュ通知を受信しても、起動は 7 回にとどまり、約 15 分の間隔が維持される場合があります。」
サイレントプッシュにはレート制限があります。プッシュ通知のたびに起動されるとは限りません。
パート 10:SwiftUI との統合
backgroundTask 修飾子
@main
struct MyApp: App {
@Environment(\.scenePhase) var scenePhase
var body: some Scene {
WindowGroup {
ContentView()
}
.onChange(of: scenePhase) { newPhase in
if newPhase == .background {
scheduleAppRefresh()
}
}
// App refresh handler
.backgroundTask(.appRefresh("com.yourapp.refresh")) {
scheduleAppRefresh() // Schedule next
await fetchLatestContent()
// Task completes when closure returns (no setTaskCompleted needed)
}
// Background URLSession handler
.backgroundTask(.urlSession("com.yourapp.downloads")) {
await processDownloadedFiles()
}
}
}
Swift Concurrency によるキャンセル
.backgroundTask(.appRefresh("com.yourapp.refresh")) {
await withTaskCancellationHandler {
// Normal work
try await fetchData()
} onCancel: {
// Called when task expires
// Keep lightweight — runs synchronously
}
}
SwiftUI でのバックグラウンド URLSession
.backgroundTask(.urlSession("com.yourapp.weather")) {
// Called when background URLSession completes
// Handle completed downloads
}
クイックリファレンス
タスクの種類
| 種類 | 実行時間 | API | 用途 |
|---|---|---|---|
| BGAppRefreshTask | 約 30 秒 | submit(BGAppRefreshTaskRequest) | コンテンツの更新 |
| BGProcessingTask | 数分 | submit(BGProcessingTaskRequest) | メンテナンス |
| BGContinuedProcessingTask | 長時間 | submit(BGContinuedProcessingTaskRequest) | ユーザーが開始する処理 |
| beginBackgroundTask | 約 30 秒 | beginBackgroundTask(withName:) | 状態の保存 |
| バックグラウンド URLSession | 必要に応じて | URLSessionConfiguration.background | ダウンロード |
| サイレントプッシュ | 約 30 秒 | didReceiveRemoteNotification | サーバーからのトリガー |
必須の Info.plist
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>your.identifiers.here</string>
</array>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>processing</string>
</array>
LLDB コマンド
// Launch
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"ID"]
// Expire
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateExpirationForTaskWithIdentifier:@"ID"]
リソース
WWDC:2019-707、2020-10063、2022-10142、2023-10170、2025-227
ドキュメント:/backgroundtasks、/backgroundtasks/bgtaskscheduler、/foundation/urlsessionconfiguration
スキル:axiom-background-processing、axiom-background-processing-diag
最終更新日:2025-12-31
プラットフォーム:iOS 13 以降、iOS 26 以降(BGContinuedProcessingTask)