mcpField
A
axiom-background-processing-ref

axiom-background-processing-ref

github.com/charleswiltgen開発1.2k168 回インストール今日更新
リポジトリ

axiom-background-processing-ref について

iOS 開発者向けのバックグラウンド実行 API リファレンスです。タスクスケジューリング、バックグラウンド URLSession、プッシュ通知、SwiftUI 連携を扱い、設定や実行制約、テスト方法を説明します。

主な機能

  1. 01更新タスクと処理タスクの登録・スケジュール
  2. 02ユーザー開始の継続処理への対応
  3. 03バックグラウンド URLSession による転送
  4. 04バックグラウンド移行後の短時間の重要処理
  5. 05タスク起動、期限切れ、保留リクエストのテスト
  6. 06システムのスケジュール制約とサイレントプッシュの確認

活用シーン

  1. 01コンテンツ更新や延期可能なメンテナンスの予約
  2. 02アプリのバックグラウンド移行後もユーザー開始の書き出しを継続
  3. 03アプリ終了後も大容量のダウンロードやアップロードを実行
  4. 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 のプロパティ
プロパティ型説明
identifierStringInfo.plist と一致する必要があります
earliestBeginDateDate?実行までの最小待機時間

パート 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 のプロパティ
プロパティ型デフォルト説明
identifierString—Info.plist と一致する必要があります
earliestBeginDateDate?nil最小待機時間
requiresNetworkConnectivityBoolfalseネットワーク接続を待ちます
requiresExternalPowerBoolfalse充電を待ちます
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 のプロパティ
プロパティ型説明
identifierStringワイルドカードを使用すると、動的なサフィックスを付けられます
titleStringシステムの進捗 UI に表示されます
subtitleStringシステムの進捗 UI に表示されます
strategyStrategy.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
        }
    }
}
設定プロパティ
プロパティデフォルト説明
sessionSendsLaunchEventsfalse完了時にアプリを再起動します
isDiscretionaryfalse最適な条件が整うまで待機します
allowsCellularAccesstrueモバイルネットワークを許可します
allowsExpensiveNetworkAccesstrue従量制ネットワークを許可します
allowsConstrainedNetworkAccesstrue低データモードを許可します

パート 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"]
テストのワークフロー
  1. タスクハンドラーにブレークポイントを設定します
  2. アプリを実行し、バックグラウンドに移行させます
  3. 実行を一時停止します(Debug → Pause)
  4. 起動シミュレーションコマンドを実行します
  5. 再開します。ブレークポイントに到達するはずです
  6. 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)

コメント