🫠

App Store に配布したアプリのみクラッシュした

に公開

はじめに

ある日、App Store にリリースしたアプリが起動直後にクラッシュしているという報告が届きました。

自分が所属しているiOSチームではリリース前にテストを徹底しているので、そんなはずはない!!と心のなかで思っていました。

しかし、実際にAppStoreからインストールしてみると、起動直後にクラッシュしました。

困惑しつつも、そのバージョンをTestFlightで社内メンバー向けに配布しましたが、再現しませんでした。
同じコード、同じ Xcode 26.4、同じビルド設定なのに、なぜかApp Store 版だけが落ちます。

結論から言えば、原因は Xcode 26.4(Swift 6.3)のコンパイラバグと、Firebase iOS SDK の組み合わせにありました。

環境

項目 内容
アプリ iOS アプリ(Swift / SwiftUI)
Xcode 26.4 (17E192)
Firebase iOS SDK 11.15.0
CI/CD Xcode Cloud
クラッシュ発生環境 App Store 版のみ

1. クラッシュが発生した

App Store 版のみで発生し、TestFlight 版では再現しないという厄介な状況でした。

クラッシュの種別は EXC_CRASH / SIGABRT、メッセージは abort() called です。

Exception Type:  EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Termination Reason: SIGNAL 6 Abort trap: 6

自分たちのコードに心当たりはなく、直近のリリースで大きな変更もしていません。何が起きているのか全くわからない状態でした。


2. クラッシュログを解析する

まずはクラッシュが発生した端末から直接 .ips ファイルを取得しました。

iPhone → 設定 → プライバシーとセキュリティ → 解析と改善 → 解析データ
→ 該当アプリ名のログを選択 → 共有ボタンで Mac に送信

.ips ファイルはそのままではアドレスの羅列で読めないため、symbolicate が必要です。dSYM は App Store Connect からダウンロードしました(ローカルの dSYM は Apple のサーバー処理で UUID が変わるため使えません)。

python3 /Applications/Xcode.app/Contents/SharedFrameworks/CoreSymbolicationDT.framework/Resources/CrashSymbolicator.py \
  -p <YourApp>-2026-04-10-094834.ips \
  -d <YourApp>.app.dSYM \
  -o symbolicated.json

symbolicate した結果、スタックトレースは得られたのですが、正直なところ自分にはこれを読み解く自信がありませんでした。

今までクラッシュログを取得しても、スタックトレースの羅列を見て「よくわからない」で終わることが多く、あまり活用できていなかったのが実情です。

そこで、symbolicate 済みのスタックトレースをそのまま AI に投げてみました。

Thread 41 (triggered) — com.apple.root.user-initiated-qos.cooperative
  [0]  __pthread_kill
  [1]  pthread_kill
  [2]  abort
  [3]  swift::swift_Concurrency_fatalErrorv(...)
  [4]  swift::swift_Concurrency_fatalError(...)
  [5]  swift_task_dealloc                          // ここで abort
  [6]  asyncLet_finish_after_task_completion(...)  // async let の後処理
  [7]  HTTPSCallable.SendableHTTPSCallable.call(_:) @ HTTPSCallable.swift:224
  [8]  closure #1 in ...call(_:completion:)        @ HTTPSCallable.swift:190

すると AI から、以下のような分析が返ってきました。

  • swift_task_deallocabort() が呼ばれているのは、Swift Concurrency ランタイムが async let の lifetime 違反を検出したパターンであること
  • HTTPSCallable.swift:224 はアプリのコードではなく、Firebase Functions SDK の内部コードであること
  • 別スレッドのスタックトレースも Firebase 内部の FunctionsContext.swift に繋がっていること
Thread 3 — com.apple.root.user-initiated-qos.cooperative
  [4]  +[NSString stringWithFormat:]
  [5]  GULOSLogBasic @ GULLogger.m:158
  [6]  -[FIRMessagingTokenManager tokenWithAuthorizedEntity:...] @ FIRMessagingTokenManager.m:186
  [7]  -[FIRMessaging FCMToken] @ FIRMessaging.m:497
  [8]  FunctionsContextProvider.context(options:) @ FunctionsContext.swift:49
  [9]  HTTPSCallable.SendableHTTPSCallable.call(_:) @ HTTPSCallable.swift:224

自分だけでは「Firebase が原因」という発想に至るまでかなり時間がかかったと思いますが、AI のおかげで一瞬でした。


3. 原因は Firebase だった

AI の分析の通り、クラッシュしているのは Firebase Functions SDK の FunctionsContext.swift 内で async let を使って並行処理している箇所でした。その lifetime 管理が壊れてクラッシュしていたのです。自分たちのコードには問題がなく、Firebase SDK 内部のバグだとわかった瞬間は正直ホッとしました。

ただ、「なぜ App Store 版だけで起きるのか?」という疑問は残ります。Xcode Cloud のビルドログを調べたところ、App Store Connect にアップロードした後に Apple 側で arm64e 向けの最適化(App Thinning)が加わり、それによって Swift 6.3 の async let に関するコンパイラバグが顕在化していたようです。

ローカル Archive → 直接インストール(arm64 そのまま)→ クラッシュしない
Xcode Cloud Archive → App Store Connect → arm64e 向けに処理 → クラッシュする

4. Firebase の GitHub Issues を見てみる

Firebase SDK のバグだとわかったので、firebase-ios-sdk の GitHub Issues を検索しました。

すると、まさに同じ症状の報告が見つかりました。

Swift コンパイラ側にも Issue が立っていました。

Xcode 26.4(Swift 6.3)において、最適化が有効な Release ビルドで async let のデアロケーション順序が誤って生成されるというコンパイラのリグレッションでした。


5. 修正の PR とアップデートが入っていた

Firebase 側では既に PR #15991 で修正されていました。

修正内容は async letTask {} に置き換えるというシンプルなものです。

// 修正前(クラッシュする)
async let authToken = auth?.getToken(forcingRefresh: false)
async let appCheckToken = getAppCheckToken(options: options)
async let limitedUseAppCheckToken = getLimitedUseAppCheckToken(options: options)

return try FunctionsContext(
    authToken: try await authToken,
    fcmToken: messaging?.fcmToken,
    appCheckToken: await appCheckToken,
    limitedUseAppCheckToken: await limitedUseAppCheckToken
)

// 修正後(安全)
let authToken = Task { try await auth?.getToken(forcingRefresh: false) }
let appCheckToken = Task { await getAppCheckToken(options: options) }
let limitedUseAppCheckToken = Task { await getLimitedUseAppCheckToken(options: options) }

return FunctionsContext(
    authToken: try await authToken.value,
    fcmToken: messaging?.fcmToken,
    appCheckToken: await appCheckToken.value,
    limitedUseAppCheckToken: await limitedUseAppCheckToken.value
)

この修正は Firebase iOS SDK 12.12.0 に含まれています。SDK を更新して再ビルド・再提出することで解決しました。

Xcode → File → Packages → Update to Latest Package Versions

まとめ

今回の件は、自分たちのコードには一切問題がないのにクラッシュするという、非常に原因特定が難しいケースでした。

振り返ると、以下の流れで解決に至りました。

  1. 端末からクラッシュログ(.ips)を取得して symbolicate する
  2. スタックトレースを AI に投げて、クラッシュ箇所が Firebase SDK の内部だと特定する
  3. Firebase の GitHub Issues で同じ報告を見つける
  4. 既に修正 PR とアップデートが出ていたので、SDK を更新して解決

正直なところ、今までクラッシュログはあまり活用できていませんでした。symbolicate しても、スタックトレースの読み方がよくわからず、結局コードを見直して地道にデバッグすることが多かったです。

ただ、今回 AI にスタックトレースを貼り付けるだけで「これは Firebase 内部の async let の問題です」と即座に教えてもらえたのは大きな発見でした。クラッシュログを自力で完璧に読み解けなくても、AI に渡すことで原因の特定が格段に速くなります。クラッシュログの価値を再認識した体験でした。

同じ症状で困っている方の参考になれば幸いです。


参考リンク

Discussion