iOS アプリが固まって強制終了される(0x8BADF00D):SwiftUI の計算プロパティが毎回走っていた

English version

トレカダッシュ 開発の記録 / 2026年10月

iOS アプリ「トレカダッシュ」で、「なんかアプリ、めっちゃクラッシュする」という報告がありました。実機のログを読むと、アプリが固まって、OS に強制終了されていたことが分かりました。原因は、画面を描き直すたびに、951件の記事を毎回数え直していたことでした。コミットの記録で書きます。

このサイトには、トレカダッシュの作品ページはまだありません。ここに書くのは、開発の記録です。

ログに残っていたもの

実機のハングのログには、0x8BADF00D という終了コードと、Failed to terminate gracefully after 5.0s という一行がありました。5秒以内に応答しないアプリを、OS(watchdog)が強制終了したという意味です。メインスレッドは、NSKeyedArchiver を経由する処理で止まっていました。また、端末は発熱していて(Thermal State: serious)、普段なら一瞬で終わる処理が、5秒を超えていました。

本当の原因

固まっていたのは、「フォロー中」タブの、商品の記事一覧です。記事が951件あるケースでした。

記事の一覧(articles)が、ただの計算プロパティになっていました。SwiftUI は、画面(body)を何度も評価し直します。記事のデータが変わっていないときも、です。そのたびに、951件に対して、絞り込み4回・変換・並べ替えを、メインスレッドで毎回やり直していました。

普段は一瞬で終わるので、気づきません。発熱で端末が遅くなったときだけ、5秒を超えて、強制終了になります。

直し方

記事のデータ、受け取り方法、並べ替えの3つが、実際に変わったときだけ、バックグラウンドのスレッドで計算し直すことにしました。結果は、表示用の状態(displayArticles、@State)に入れます。これで、メインスレッドを塞がなくなります。

// (イメージです。実際のコードそのものではありません)
// 前:body を評価するたびに、毎回
var articles: [Article] { fetched.filter…filter…map…sorted }

// 後:変わったときだけ、裏で計算して、結果を持っておく
@State var displayArticles: [Article] = []
// fetchedArticles / receiveMode / sortOption が変わったときに再計算

確かめられていないこと

コミットに書かれている確認は、xcodebuild でビルドが成功した(BUILD SUCCEEDED)ことだけです。実機で、固まらなくなったことは、記録にありません。発熱している状態は、再現しにくいので、確かめるのも簡単ではありません。この記事では、「直した」ではなく、「原因を特定して、直す方式に変えた」と書いておきます。

分かったこと

普段は一瞬で終わる処理は、遅い日にだけ、アプリを止める。

計算プロパティは、書くのが楽です。ただ、画面が描き直されるたびに実行される場所に置くと、データの量に比例して、遅くなります。951件は、少なくない数です。「何回呼ばれるか」を意識して置き場所を決める、という当たり前のことを、実機のログで教わりました。

この記事は、コミット(2026年10月5日)の記録から書きました。

この話に出てくるもの

ほかの記録

コメント

感想や、記事の間違いの指摘は、ここへ。書くには、GitHub のアカウントが要ります。

押すと、giscus(giscus.app)と GitHub に接続します。押さなければ、接続しません。詳しくはあつかいに書いています。