◉RORK LABEN
●GPT-6.1 SOL — Rork のモデルメニューに追加(09-29)。Low/Medium/High を選べ、Pro・Max に含まれます●SONNET 5.5 — UI づくりが得意で、Sonnet 5 より出力が速いモデルが選べます(09-28)●SDK 58 — Beta が 09/15 から。stable の案内はまだなく、Expo Go でのプロジェクト実行は 09/03 からログインが必要です●WIDGET — iOS のウィジェットが本番ビルドだけ真っ黒になる原因を追った報告が出ています●OBSERVE — EAS Observe がネイティブクラッシュも記録します(SDK 57.0.21 以降・10/07)●NEW — 起動広告が、背面から戻った回にしか出ていませんでした。「前面に戻った」合図が、…●GPT-6.1 SOL — Rork のモデルメニューに追加(09-29)。Low/Medium/High を選べ、Pro・Max に含まれます●SONNET 5.5 — UI づくりが得意で、Sonnet 5 より出力が速いモデルが選べます(09-28)●SDK 58 — Beta が 09/15 から。stable の案内はまだなく、Expo Go でのプロジェクト実行は 09/03 からログインが必要です●WIDGET — iOS のウィジェットが本番ビルドだけ真っ黒になる原因を追った報告が出ています●OBSERVE — EAS Observe がネイティブクラッシュも記録します(SDK 57.0.21 以降・10/07)●NEW — 起動広告が、背面から戻った回にしか出ていませんでした。「前面に戻った」合図が、…
記事一覧/アプリ開発
◇ アプリ開発/2026-07-16上級

壁紙の段組みギャラリーにネイティブ広告を差し込む — 広告セルの寿命をプールで設計する

段組みギャラリーにネイティブ広告を1枚入れたところ、20分のスクロールでメモリが伸び続けました。リスト再利用と広告の寿命が噛み合わない理由から、プール方式の実装、差し込み間隔の決め方まで実測とともに残します。

Rork585Expo219AdMob73ネイティブ広告FlashList5

✦ プレミアム記事

段組みのギャラリーに、ネイティブ広告を1枚だけ差し込みました。10枚目のあとに1枚。それだけの変更です。

翌朝、Xcode の Memory Report を開いたまま20分ほどスクロールを続けたところ、メモリが 180 MB から 420 MB まで伸びて戻りませんでした。広告を外すと 190 MB 前後で平らになります。原因は広告そのものではなく、広告を「セルの一部」として扱っていた私の設計にありました。

個人開発で壁紙アプリを何本か回していると、広告の実装は毎回どこかで手が止まります。今回つまずいたのは、リストの再利用という React Native 側の都合と、ネイティブ広告オブジェクトの寿命という広告 SDK 側の都合が、まったく違う時間軸で動いていたことでした。

その噛み合わなさをどう設計で吸収したかを残します。段組みギャラリーそのものの組み方は高さの異なる画像を2列で美しく並べる—Rork の Expo アプリで壁紙ギャラリーを段組みにするに書きましたので、この記事はその上に広告を載せる話に絞ります。

段組みに広告を1枚入れた翌日に起きたこと

症状は3つ、同時に出ました。

症状広告なし広告あり(素朴な実装)
20分スクロール後のメモリ(iPhone 14, 実機)190 MB 前後で平ら420 MB まで単調増加
スクロール中のフレーム落ち(60fps 基準・1分あたり)2〜4 回28〜41 回
広告の表示回数 / スクロール到達回数—0.62(4割が空白セル)

3つとも別の問題に見えて、根は1つでした。広告を返す関数を renderItem の中で呼んでいたのです。

// 素朴な実装。ここから3つの症状が全部出ました
function renderItem({ item }: { item: GridItem }) {
  if (item.type === 'ad') {
    return <NativeAdCell unitId={AD_UNIT_ID} />; // ← セルの中で読み込む
  }
  return <WallpaperCell wallpaper={item.wallpaper} />;
}

NativeAdCell は自分の useEffect の中で広告を読み込みます。素直に見えます。けれど FlashList も FlatList も、画面外に出たセルを破棄せず、別のデータで使い回します。つまりこのコンポーネントは、スクロールのたびにマウントとアンマウントを繰り返します。

そのたびに新しい広告リクエストが飛び、そのたびに新しいネイティブ広告オブジェクトが生まれます。そして誰も捨てません。

広告オブジェクトの寿命はセルの寿命と一致しない

ここが一番の勘所でした。

React のコンポーネントは「画面に映っている間だけ生きていればよい」という前提で書きます。アンマウントされたら消える、と私たちは思っています。

ところがネイティブ広告は違います。GADNativeAd(iOS)や NativeAd(Android)は JavaScript 側の参照が切れてもネイティブ側に残ります。明示的に destroy() を呼ぶまで、画像アセットもクリック計測用のビューも保持され続けます。JavaScript の GC はネイティブヒープを掃除してくれません。

さらに厄介なのは、広告の読み込みが 400〜900 ms かかることです。セルが画面外に出るのに要する時間より長い。素早くスクロールすると、こういう順序になります。

  1. セルがマウントされ、広告リクエストを送る
  2. 320 ms 後、セルが画面外に出てアンマウントされる
  3. 610 ms 後、広告が返ってくる — 貼り付ける先のビューはもう無い
  4. 広告オブジェクトはネイティブ側に残る。誰も destroy() を呼ばない

先ほどの「表示回数 / 到達回数 = 0.62」は、この 3 の状態が4割起きていたということです。読み込んだのに一度も見られていない広告が、メモリだけ食っていました。

私はこれを「寿命のズレ」と呼んで区別するようにしました。セルの寿命は数百 ms、広告の寿命は分単位。この2つを同じライフサイクルに縛ろうとすると、必ずどちらかが壊れます。

✦

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

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
✦リストのセル再利用と広告オブジェクトの寿命が噛み合わず、メモリが伸び続ける仕組みと切り分け手順
✦広告をプールで保持しセルには貸すだけにする実コード、および破棄タイミングの判断ライン
✦差し込み間隔・スクロール中の読み込み抑止・段組みの高さ計算を、実測値をもとに決める方法
Stripe による安全な決済 · いつでもキャンセル可能
✦

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

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

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

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

関連記事

◇ アプリ開発2026-09-18
prebuild が requires the standard Expo SDK 57 Swift AppDelegate で止まったとき、まず確かめる3行
iOS 27 のために ios.enableSceneSupport を有効にすると prebuild が止まることがあります。原因は SDK の版ではなく AppDelegate.swift の形でした。確かめる3行と、手で書くときに SDK 58 の手順を真似てはいけない理由をまとめます。
◇ アプリ開発2026-09-16
消したはずのトークンが再起動で戻る — SecureStore の削除失敗を検知する設計
サインアウト直後に getItemAsync が null を返しても、Android のディスクには値が残っていることがあります。削除の成否を読み出しで確かめていた実装を、戻り値で判断する形へ書き直した記録です。
◇ アプリ開発2026-09-10
Rork のリポジトリに置く最初の EAS Workflow と、失敗した回にだけ届かない通知
Rork から書き出したリポジトリに .eas/workflows を二本置くまでの手順と、needs でつないだ通知が失敗した回にだけ届かなくなる理由を、手元で走る検査スクリプトの実行結果つきで書き残します。