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-20中級

iOS の 0x8badf00d ウォッチドッグ強制終了に Rork アプリが落ちるときの対処

Rork で作った iOS アプリが起動直後に消える。クラッシュログに 0x8badf00d。Apple のウォッチドッグが React Native の重い初期化を切ったときの正しい直し方を、私の運用中アプリで実際に効いた手順で説明します。

rork58ios12crash3watchdoglaunch2react-native12expo11

iOS の 0x8badf00d ウォッチドッグ強制終了に Rork アプリが落ちるときの対処

App Store に出した直後だけ、レビューが「起動してすぐ消える」と報告してくる。手元の Xcode や Simulator では再現しありません。Crashlytics には EXC_CRASH (SIGKILL) と例外コード 0x8badf00d。これは Apple が「ate bad food」と読ませる有名なシグネチャで、ウォッチドッグタイムアウトの印です。

私は 2014年から個人で iOS / Android アプリを運用していて、累計 5,000万 DL を越えた壁紙・癒し・引き寄せ系アプリでも、SDK を増やすたびにこの 0x8badf00d を踏みました。React Native / Expo / Rork のように JS バンドルを読み込む構成では、ネイティブ単体のときよりさらに発生しやすいので、原因の切り分け方と、私の手元で効いた対処を整理しておきます。

0x8badf00d とは何が起きているのか

iOS は application(_:didFinishLaunchingWithOptions:) が返るまでに使ってよい時間を、デバイスの状態に応じて 5〜20 秒の範囲で動的に決めています。実効的には新しめの端末でも 10 秒前後、古い端末や寒い場所、バッテリー残量が低いとさらに短くなります。この上限を越えると iOS は理由を問わずプロセスを SIGKILL で落とし、例外コード 0x8badf00d を残します。

React Native / Expo の場合、JS スレッドが Hermes でバンドルを評価して最初の requireApplicationRegister を終えるまでの全部が、この計測に乗ります。Rork で量産したアプリでも、ネイティブ側で AdMob・AppOpen・Firebase・RevenueCat・Sentry・Branch を全部同期初期化していると、新しい iPhone でも余裕で 8〜9 秒に届いてしまい、古い端末では落ちます。

ポイントは、開発機で測ったコールドスタートが「2 秒」だから安全、ではないということです。ウォッチドッグの基準は端末側のリアルタイムの状態で決まります。Crashlytics の Apple 環境別クラッシュレートを見ると、私のアプリでもこのタイプは iPhone 8 / SE 第二世代 / iPad 第七世代に集中していました。

クラッシュログから 0x8badf00d を断定する

Crashlytics や Xcode Organizer の Crashes パネルで以下を確認すると、ほぼ確定で切り分けられます。

  • Exception Type: EXC_CRASH (SIGKILL)
  • Exception Codes: 0x8badf00d
  • Termination Reason: FRONTBOARD 0x8badf00d ... (scene-create watchdog transgression: ...)
  • Triggered by Thread の最上位スタックが RCTCxxBridge, facebook::hermes::HermesRuntime, expo-modules-core, あるいは AdMob / Firebase の +[FIRApp configure] など、起動経路の関数

Triggered by Thread: 0 でメインスレッドが固まっている、というのが典型です。JS スレッドや IO スレッドが原因でも、メインスレッドがそれを待っていることが多いです。

まず疑う 4 つのブロッキング処理

私が踏んだ実例から、Rork 構成で 0x8badf00d を引き起こす原因の 9 割は次のどれかでした。

  1. AppDelegate での SDK の連続同期初期化: AdMob, Firebase, RevenueCat, Branch, Sentry を「念のため起動直後に全部」初期化している
  2. MMKV / AsyncStorage / SQLite からの同期読み込み: ユーザー設定、テーマ、A/B フラグなどを didFinishLaunching 内の同期 IO で取ろうとしている
  3. AppOpen 広告の同期 preload: AdMob の GADAppOpenAd.load をネイティブ側で await 相当の処理に組んでしまっている
  4. JavaScript の初回 import の重さ: App.tsx のトップで巨大な定数モジュール(多言語 JSON、配列でハードコードされた壁紙メタ 1 万件など)を import している

特に 1 と 3 は、Rork で生成された雛形をそのまま使ってしまうと起こりやすいパターンです。SDK 各社のドキュメントが揃って「起動直後に呼べ」と書いているせいで、結果として全部が直列に積み上がります。

直し方:起動経路から重い処理を全部追い出す

ネイティブ側(AppDelegate / ExpoAppDelegateSubscriber)

// 危険:すべてが didFinishLaunching に同期で乗っている
- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  [FIRApp configure];
  [GADMobileAds.sharedInstance startWithCompletionHandler:nil];
  [RCPurchases configureWithAPIKey:@"YOUR_REVENUECAT_KEY"];
  [Branch.getInstance initSessionWithLaunchOptions:launchOptions
                              andRegisterDeepLinkHandler:^(NSDictionary *params, NSError *error){}];
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

これを次のように、必須のものだけ残して残りは dispatch_after または最初のフレーム描画後に押し出します。

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  // クラッシュレポートだけは最優先で先に上げる
  [FIRApp configure];
 
  // 残りは UI が出てから初期化
  dispatch_async(dispatch_get_main_queue(), ^{
    [GADMobileAds.sharedInstance startWithCompletionHandler:nil];
    [RCPurchases configureWithAPIKey:@"YOUR_REVENUECAT_KEY"];
    [Branch.getInstance initSessionWithLaunchOptions:launchOptions
                                andRegisterDeepLinkHandler:^(NSDictionary *params, NSError *error){}];
  });
 
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

Firebase だけ先に動かすのは、AppOpen 広告や RevenueCat 側でクラッシュが起きてもログを拾えるようにするためです。

JavaScript 側

// App.tsx — 重い import をルートからは外す
import { useEffect, useState } from "react";
import { View } from "react-native";
 
export default function App() {
  const [ready, setReady] = useState(false);
 
  useEffect(() => {
    // 最初の描画が終わってから残りを読み込む
    (async () => {
      const [{ initAnalytics }, { initRemoteConfig }] = await Promise.all([
        import("./bootstrap/analytics"),
        import("./bootstrap/remote-config"),
      ]);
      await Promise.all([initAnalytics(), initRemoteConfig()]);
      setReady(true);
    })();
  }, []);
 
  return <View>{/* スプラッシュ相当の軽い UI をまず出す */}</View>;
}

Promise.all で並列にし、await import() を使ってバンドルから初回起動の依存を切り離します。Hermes は遅延 require と相性がよく、私の壁紙アプリでは平均コールドスタートが 2.4 秒 → 1.3 秒に縮みました。AdMob の eCPM や DAU に直接効くタイプの改善ではないですが、0x8badf00d のリテンション悪化分は確実に取り戻せます。

TestFlight では出ないクラッシュをローカルで再現する

ウォッチドッグの厳しさは「コールド + 低バッテリー + 低温」で増します。手元で意図的に再現したいときは、Xcode の Device メニューから次の Network Link Conditioner を組み合わせます。

  1. iPhone 実機を放電 20% 以下にしてから airplane mode 解除
  2. 「設定 → デベロッパ → Network Link Conditioner」で Very Bad Network を有効化
  3. アプリを完全に閉じてから(フォアグラウンドにある状態ではダメ)アイコンタップ

これだけで、開発機で再現しなかった iPhone 8 のクラッシュを 5 回中 3 回は引けます。Firebase Test Lab の physical デバイスで --orientation portrait --device-ids iphone8 を指定して回す手もありますが、コストが嵩むので、私はこのローカル法を先にやります。

0x8badf00d が出ない構成を保つチェックリスト

リリース前に Xcode の Instruments → App Launch テンプレートで毎回計測しておくと、SDK を一つ足したときに気付けます。私の場合、Apps/iOS Apps の各リリースブランチでは次の3点を CI に乗せています。

  • App Launch (Pre-main + Time to first frame) が新 iPhone で 1.8 秒以下、SE 第二世代で 3.5 秒以下に収まっていること
  • AppDelegate の didFinishLaunching 内に新規 await / 同期 dictionaryWithContentsOfURL: 系を入れていないこと
  • package.jsondependencies 差分で、起動経路から呼ばれる重量 SDK が増えていないこと(増えるなら遅延化)

Crashlytics 側では EXC_CRASH (SIGKILL) 0x8badf00d を別アラートに切り出し、リリースごとに 0.05% を超えたら段階配信を止める、というルールにしています。

次にやること

クラッシュログから 0x8badf00d だと確定したら、まず AppDelegate を覗いて、didFinishLaunching から押し出せる SDK 初期化を 1 つ選んで dispatch_async(main_queue, …) に移してください。1 個押し出すだけでも、SE 第二世代クラスでは目に見えてコールドスタートが軽くなります。続けて JS 側の App.tsx から最重量の import を 1 つ遅延読み込みに置き換えると、ウォッチドッグの余白がはっきり広がります。

同じ問題で困っている方の参考になればうれしいです。お読みいただきありがとうございました。

シェア

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

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

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

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

関連記事

開発ツール2026-05-23
Rork 生成プロジェクト特有の expo start --offline forbidden — テンプレ構成が原因の4パターン
Rork が生成するテンプレート構成が原因で expo start --offline が forbidden になる4つのパターン(tsconfigPaths・expo-router キャッシュ・ネイティブプリビルド・lock 不一致)を、Rork プロジェクト固有の観点で切り分けます。汎用的な Expo 側の対処(プロキシ403・依存検証)は別記事にまとめています。
開発ツール2026-03-26
Rork アプリの起動クラッシュ・白画面 完全解決ガイド
Rorkアプリが起動直後にクラッシュしたり、白画面のまま固まる問題を根本から解決。白画面・即座クラッシュ・遅延クラッシュ・リリルビドのクラッシュなど5パターンの原因と対処法を網羅。Xcode・Logcat・React Native Debuggerでの実際のデバッグ手順付き。
開発ツール2026-03-26
Rork React Native ビルドエラー完全解決ガイド 2026
Rork で生成した React Native アプリのビルドエラーを完全解決。Metro Bundler、ネイティブモジュールリンク、Gradle、Xcode、Expo 設定エラーまで、症状・原因・解決策を網羅しています。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →