RORK LABEN
TOOLING — Rorkの開発者向けリポジトリが動き続けています。rork-xcodeが7月16日、rork-deviceが7月15日、rork-plistが7月13日に更新されましたOPUS46 — RorkでClaude Opus 4.6が稼働しています。Rork MaxはClaude Codeを土台にアプリを組み立てる設計ですSIM — ブラウザ上で動くクラウドのiOSシミュレータを備え、実機へのインストールは1クリック、App Storeへの提出は2クリックと案内されていますMAX — Rork MaxはReact Nativeではなく純粋なSwiftを出力します。iPhone・iPad・Apple Watch・Apple TV・Vision Pro、そしてiMessageまでが射程ですNATIVE — HealthKit、ARKitとLiDAR、NFC、Dynamic Island、Live Activities、Metalによる3D、Core MLのオンデバイス推論まで扱えますSEED — RorkはLeft Lane Capitalが主導する1,500万ドルのシードラウンドを実施し、Peak XVとa16z Speedrunが参加しましたTOOLING — Rorkの開発者向けリポジトリが動き続けています。rork-xcodeが7月16日、rork-deviceが7月15日、rork-plistが7月13日に更新されましたOPUS46 — RorkでClaude Opus 4.6が稼働しています。Rork MaxはClaude Codeを土台にアプリを組み立てる設計ですSIM — ブラウザ上で動くクラウドのiOSシミュレータを備え、実機へのインストールは1クリック、App Storeへの提出は2クリックと案内されていますMAX — Rork MaxはReact Nativeではなく純粋なSwiftを出力します。iPhone・iPad・Apple Watch・Apple TV・Vision Pro、そしてiMessageまでが射程ですNATIVE — HealthKit、ARKitとLiDAR、NFC、Dynamic Island、Live Activities、Metalによる3D、Core MLのオンデバイス推論まで扱えますSEED — RorkはLeft Lane Capitalが主導する1,500万ドルのシードラウンドを実施し、Peak XVとa16z Speedrunが参加しました
記事一覧/開発ツール
開発ツール/2026-05-28中級

Rork アプリで iOS の BGTaskScheduler.submit が Error Code=1 で失敗するときの切り分け手順

Rork で作った iOS アプリで BGTaskScheduler.submit が Error Code=1 (Unavailable) を返してバックグラウンド更新が動かないときに、原因をひとつずつ潰していくための実用チェックリストです。

Rork515iOS108BGTaskScheduler3バックグラウンド更新2Expo149React Native209

バックグラウンド更新を入れたつもりが、リリース後しばらく経って Crashlytics に BGTaskSchedulerErrorDomain Code=1 が静かに溜まっているのを見つける、というのは個人開発でいちばん起こりやすい事故のひとつだと感じています。私自身、累計5,000万ダウンロード規模のアプリ群を 2014年から運用してきたなかで、この Code=1 を本気で潰した経験は両手では足りません。発火しないことに気づくのが半年遅れたりすると、そのぶん DAU が静かに削られていきます。

Code=1 は仕様上 BGTaskSchedulerErrorCodeUnavailable と定義されており、ドキュメントには「Background App Refresh が無効、もしくは現在の環境でタスクをスケジュールできない状態」と一行だけ書かれています。実際に詰まる原因は 6 種類くらいに分かれていて、上から潰していけば必ず原因にたどり着けます。本稿は Rork で生成したアプリに後から BGTaskScheduler を組み込んだ前提で、その 6 パターンを順番に整理します。

宮大工だった両家の祖父を見て育ったせいか、私はこういう「動かない原因」を一段ずつ確認していく作業が嫌いではありません。表に出ない部分こそ手をかけたいタイプなので、参考にしていただけたら嬉しいです。

まず「どの状態で Code=1 になっているか」を特定する

Code=1 はひとつのエラーコードに 4〜5 種類の原因が押し込まれているため、最初に「いつ起きているか」を切り分けるだけで体感作業時間が半分になります。次のように submit の周囲をログで包んで、リリースビルドの実機・シミュレーター・TestFlight それぞれで挙動を確認します。

// AppDelegate.swift もしくは Expo モジュール内
import BackgroundTasks
import os
 
let log = Logger(subsystem: "net.rorklab.app", category: "bgtask")
 
func scheduleAppRefresh() {
    let request = BGAppRefreshTaskRequest(identifier: "net.rorklab.app.refresh")
    request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
 
    do {
        try BGTaskScheduler.shared.submit(request)
        log.info("bgtask submitted: id=\(request.identifier)")
    } catch let error as NSError {
        log.error("bgtask submit failed: domain=\(error.domain) code=\(error.code) info=\(error.userInfo)")
        // userInfo の NSLocalizedFailureReason をそのまま Crashlytics などに送る
    }
}

ここで userInfo を捨ててしまうと、後段の切り分けでもう一周ハマります。私は必ず userInfo ごと送って Crashlytics の Custom Key に乗せるようにしています。

原因 1:Info.plist の BGTaskSchedulerPermittedIdentifiers が未登録

いちばん多いのがこれです。submit する identifierInfo.plistBGTaskSchedulerPermittedIdentifiers 配列に列挙されていない場合、iOS は Code=1 を返します。Expo / Rork 系の構成では app.json (もしくは app.config.ts) の ios.infoPlist から注入する形になります。

{
  "expo": {
    "ios": {
      "infoPlist": {
        "BGTaskSchedulerPermittedIdentifiers": [
          "net.rorklab.app.refresh",
          "net.rorklab.app.processing"
        ],
        "UIBackgroundModes": ["fetch", "processing"]
      }
    }
  }
}

注意点としては、Bundle ID と prefix が一致している必要はないものの、実機での submit 時に Info.plist 配列に文字通り一致する文字列が存在しないと弾かれますprebuild 後に Xcode 上でも Info.plist を開いて値が反映されているか必ず確認します。EAS Build を挟む構成だと、prebuild をスキップして古い Info.plist のままビルドが回ってしまうことがあるので、eas build --clear-cache を 1 度走らせるのも有効です。

原因 2:Background App Refresh が OS 設定で OFF

実機で再現するけれどシミュレーターでは起きない、というときはほぼこれです。「設定 > 一般 > Background App Refresh」が OFF になっている、もしくは「省電力モード」が ON になっていると、ユーザーごとに submit が Code=1 で弾かれます。

これはアプリ側で復旧できる問題ではないので、UX 上は次の 2 点で対応します。

  • 初回起動時に UIApplication.shared.backgroundRefreshStatus を確認し、.denied または .restricted のときは「最新情報を閉じている間も取りに行くには Background App Refresh を ON にしてください」と一段だけ案内を出す
  • Crashlytics には backgroundRefreshStatus を Custom Key として常時送っておき、Code=1 の発生数と OFF ユーザー数の相関を後から確認できるようにする

これを入れただけで、私が運用しているアプリの 1 つでは「Code=1 のうち 7 割は OS 設定 OFF が原因」だったことが分かり、無駄な再現調査をしなくて済むようになりました。

原因 3:シミュレーターで実行している

地味に時間を奪うのがこれです。BGTaskScheduler はシミュレーターでも submit 自体は通る場合があるのですが、iOS 17 以降は条件によって Code=1 を返すようになっています。シミュレーターでバックグラウンド処理を検証するときは、submit の成功・失敗ではなく、デバッガから直接タスクを発火させる方を主軸にします。

LLDB で次のコマンドを叩くと、登録済みのタスクを強制的に発火できます。

(lldb) e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"net.rorklab.app.refresh"]

これで handler が即座に呼ばれるので、Code=1 自体は気にせず、handler 側のロジックの検証に集中できます。実機での submit 成功率の確認は TestFlight 配信後に Crashlytics の Code=1 件数で見るのが現実的です。

原因 4:UIBackgroundModes の指定漏れ

BGAppRefreshTaskRequestsubmit するなら UIBackgroundModesfetch が必要、BGProcessingTaskRequest を使うなら processing も必要、というのは公式ドキュメントどおりなのですが、Expo / Rork の構成では prebuild 時に上書きされて消えるケースがあります。

{
  "ios": {
    "infoPlist": {
      "UIBackgroundModes": ["fetch", "processing"]
    }
  }
}

prebuild 後に ios/{AppName}/Info.plistUIBackgroundModes を必ず目視確認します。fetch だけでは BGProcessingTaskRequest で Code=1 を返されるので、両方使うアプリは必ず 2 種類書きます。

原因 5:earliestBeginDate を過去 / 直近すぎる時刻にしている

earliestBeginDateDate() のような直近の時刻にすると、iOS のスケジューラの内部状態次第で Code=1 を返してくることがあります。最低でも 15 分先に設定するのが安全です。

request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)

過去日時を入れた場合は Code=1 ではなく Code=3 (TooManyPendingTaskRequests) 相当の挙動になることもあり、エラーコードだけ見て切り分けると遠回りします。earliestBeginDate を nil もしくは 15 分以上先に固定するのが安定挙動でした。

原因 6:register と submit の identifier 不一致

BGTaskScheduler.shared.register で登録した identifier と submit した identifier がわずかでも違うと Code=1 になります。Rork の生成コードを後から手動で編集すると、文字列がコピペでずれることがあるので、定数化して両者で参照する形に統一しておくと安全です。

enum BackgroundTaskID {
    static let appRefresh = "net.rorklab.app.refresh"
    static let processing = "net.rorklab.app.processing"
}
 
// AppDelegate.swift
BGTaskScheduler.shared.register(forTaskWithIdentifier: BackgroundTaskID.appRefresh, using: nil) { task in
    handleAppRefresh(task: task as! BGAppRefreshTask)
}

私の場合、6 本並行運用している壁紙アプリのうち 1 本で「app.refreshappRefresh の表記揺れ」を見落として 2 週間 Code=1 を出し続けたことがあり、それ以来必ず enum で固めるようにしています。

切り分けが終わったあとの確認順序

ここまでで原因が一巡したので、最後に「次に何をするか」を明確にしておきます。

  1. AppDelegate / Expo モジュールに submituserInfo 込みログを仕込む(数分)
  2. Info.plist の BGTaskSchedulerPermittedIdentifiersUIBackgroundModes を Xcode で目視確認する(5 分)
  3. 実機で「設定 > 一般 > Background App Refresh」を ON にして再現するか確認する
  4. register / submit の identifier を enum で固める(10 分)
  5. TestFlight 配信して 24 時間 Crashlytics で Code=1 の件数推移を見る

ここまでやれば、Code=1 の 9 割は原因が特定でき、残りの 1 割は OS 設定や省電力モード由来でアプリ側ではどうしようもない領域として扱えます。BGTaskScheduler はエラーコードがシンプルすぎる代わりに、原因の母数が決まっているので、いったんチェックリスト化してしまえば再発しても 30 分以内に原因にたどり着けます。

私自身、累計5,000万ダウンロードのアプリ群で同じ作業を繰り返してきましたが、最終的にこの順序に落ち着いたのは「文字通りこれ以上短くできない」という確信があるからです。同じ症状で詰まっている方の半日が、これで救われたら嬉しいです。実装の参考になれば幸いです。

シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

開発ツール2026-05-07
Rorkアプリで「閉じてる間も最新」を本番品質で実装する — iOS BGTaskScheduler × Silent Push × Android WorkManager
Rorkで作ったアプリのバックグラウンド更新を、iOSのBGTaskScheduler・Silent Push、AndroidのWorkManagerで本番品質に仕上げるための実装ガイド。配信成功率の落とし穴と回復パターンまでを解説します。
開発ツール2026-06-14
アプリを開かなくても更新される、を Expo で実際に届ける — Background Task の現実的な設計
Rork が生成する Expo アプリで『毎朝コンテンツが更新される』体験を作ろうとすると、iOS のバックグラウンド実行は思ったほど律儀に動きません。expo-background-task の最小実装と、実行されない前提で破綻しない設計をまとめました。
開発ツール2026-04-02
Rork アプリにネイティブ共有機能を追加する — Share Sheet 実装ガイド
Rork アプリにネイティブ Share Sheet を実装する入門ガイド。テキスト・URL・画像のシェアから SNS 連携・ディープリンク連携まで、実際のコードで丁寧に解説します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →