AdMobのeCPMを少し底上げしようと、メディエーションのアダプタを3つまとめて追加した日のことです。私が運営している iOS 4本(綺麗な壁紙・浮世絵壁紙・Relaxing Healing・Law of Attraction Everyday、いわゆる RWD-iOS グループ)に Unity Ads・Liftoff・InMobi のアダプタを入れたところ、それまで普通に通っていたビルドが ld: framework not found で一斉に落ちました。AdMob 本体のSDKは何も触っていないのに、です。
メディエーションは「アダプタを追加するだけ」と説明されることが多いのですが、実際にはリンク段階と Info.plist の両方に手を入れないと、ビルドが通らない・通っても広告がフィルしない、という二段構えの罠が待っています。同じところで止まっている方のために、症状別の切り分けをまとめておきます。
まず症状を3つに分けて考える
メディエーションアダプタ追加後のトラブルは、見た目が似ていても原因の層が違います。最初にどの症状かを確定させると、無駄な試行錯誤を減らせます。
- 症状A: ビルドがリンカエラーで落ちる —
ld: framework not found GoogleMobileAdsMediation...やUndefined symbols for architecture arm64、あるいはduplicate symbolが出る - 症状B: ビルドは通るが、メディエーション広告が一切フィルしない — AdMob 管理画面のメディエーションレポートで該当ネットワークの広告リクエストが0、またはエラー率100%
- 症状C: ビルドも表示も通るが、計測(収益・アトリビューション)が合わない — SKAdNetwork のポストバックが届かず、ネットワーク側ダッシュボードの数字が極端に低い
AはリンクとPodの整合性、BとCは Info.plist と初期化順序の問題であることがほとんどです。順番に潰していきます。
症状A: リンカエラーの原因はバージョンの不一致
framework not found や Undefined symbols が出る場合、9割はアダプタと AdMob 本体(Google-Mobile-Ads-SDK)のバージョン組み合わせが噛み合っていません。各アダプタは「対応する AdMob SDK のレンジ」を持っていて、ここがずれると、アダプタが期待するシンボルが本体側に存在せずリンクが失敗します。
まず Podfile で、本体とアダプタのバージョンを明示的に揃えます。~> で緩く指定して pod update 任せにすると、アダプタだけ新しい版に飛んで不整合が起きやすいので、私は数字を固定する書き方を好みます。
# Podfile — AdMob 本体とメディエーションアダプタは版を固定して整合させる
target 'YourApp' do
use_frameworks! :linkage => :static
# AdMob 本体(アダプタが対応するレンジに合わせる)
pod 'Google-Mobile-Ads-SDK', '12.4.0'
# メディエーションアダプタ(本体 12.x に対応する版を選ぶ)
pod 'GoogleMobileAdsMediationUnity'
pod 'GoogleMobileAdsMediationVungle' # Liftoff は内部的に Vungle アダプタ名
pod 'GoogleMobileAdsMediationInMobi'
endここで :linkage => :static を付けているのがポイントです。Firebase を SPM に移し、AdMob 周りだけ CocoaPods に残すハイブリッド構成にしていると、動的フレームワークと静的ライブラリが混在して duplicate symbol を引き起こすことがあります。広告系アダプタは静的リンクで揃えると、この衝突がほぼ消えます。
Podfile を直したら、中途半端なキャッシュを残さないようにクリーンに入れ直します。
# 古い Pods とロックを捨ててから入れ直す(部分更新で壊れた状態を引きずらない)
rm -rf Pods Podfile.lock
pod deintegrate
pod install --repo-update
# Xcode 側のビルドキャッシュも消す(残っていると framework not found が再発する)
rm -rf ~/Library/Developer/Xcode/DerivedData/*それでも Undefined symbols が消えない場合は、アダプタ単体ではなく、そのアダプタが必要とする SDK 本体(例: InMobiSDK)が入っていないケースを疑います。pod install のログに Installing ... として本体SDKが現れているかを確認してください。アダプタは「橋渡し」であって、ネットワーク本体のSDKは別 Pod として入る必要があります。
症状B: ビルドは通るのに広告が出ないのは Info.plist が原因
リンクが通った後に多いのが「メディエーション広告が一切返ってこない」症状です。AdMob のメディエーションレポートで、追加したネットワークだけリクエスト0が続くなら、Info.plist の SKAdNetworkItems が足りていない可能性が高いです。
iOS では、各広告ネットワークの SKAdNetwork ID を Info.plist に列挙しておかないと、そのネットワークの広告配信・計測が成立しません。アダプタを追加しても、この ID は自動では入りません。
<!-- Info.plist — 追加したネットワークぶんの SKAdNetwork ID を列挙する -->
<key>SKAdNetworkItems</key>
<array>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>cstr6suwn9.skadnetwork</string> <!-- Google AdMob -->
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>4dzt52r2t5.skadnetwork</string> <!-- Unity Ads -->
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>gta9lk7p23.skadnetwork</string> <!-- Vungle / Liftoff -->
</dict>
</array>各ネットワークの正確な ID は、必ず公式の最新リストから取得してください(ID は増減します)。私は追加したネットワークの分だけ手で足すより、Google が配布しているメディエーション用のまとめリストをベースに、使っているネットワークだけ残す形にしています。Expo 管理下のプロジェクトなら、Info.plist を直接編集せず app.config.js の ios.infoPlist に書くと、prebuild で消えずに残ります。
// app.config.js — Expo prebuild で SKAdNetworkItems が消えないようにする
export default {
ios: {
infoPlist: {
SKAdNetworkItems: [
{ SKAdNetworkIdentifier: 'cstr6suwn9.skadnetwork' },
{ SKAdNetworkIdentifier: '4dzt52r2t5.skadnetwork' },
{ SKAdNetworkIdentifier: 'gta9lk7p23.skadnetwork' },
],
},
},
};症状C: 初期化順序とATTで計測が崩れる
ビルドも表示も通っているのに、ネットワーク側の収益が実態より極端に低い場合は、ATT(App Tracking Transparency)のプロンプトと広告SDK初期化の順序を疑います。requestTrackingAuthorization を呼ぶ前に MobileAds を初期化してしまうと、初期化時点では非許諾扱いになり、一部ネットワークが計測精度の低いリクエストを送り続けます。
順序としては、アプリ起動直後に ATT の許諾を取り、その応答を受けてから広告SDKを初期化します。
// 起動直後: ATT の応答を待ってから MobileAds を初期化する
import AppTrackingTransparency
import GoogleMobileAds
func initializeAdsAfterATT() {
ATTrackingManager.requestTrackingAuthorization { _ in
// 許諾・非許諾どちらでも、応答が確定してから初期化する
DispatchQueue.main.async {
MobileAds.shared.start(completionHandler: nil)
}
}
}ここで「許諾が取れなかったら初期化しない」とはしない点に注意してください。非許諾でも広告は出ます。あくまで「ATTの応答が確定してから初期化する」のが目的です。React Native 側で広告を制御している場合も、ネイティブの起動シーケンスでこの順序を守れているかを AppDelegate 周辺で確認します。
予防策: アダプタは一度に1つずつ足す
今回いちばんの反省は、3つのアダプタを一度に入れてしまったことです。リンカエラーが出たとき、どのアダプタが原因かを特定するのに余計な時間がかかりました。累計5,000万ダウンロードのアプリ群を12年運用してきて、収益系の変更ほど「一度に1つ、ビルドが通ることを確認してから次」が結局いちばん速い、と痛感しています。
具体的には、アダプタを1つ追加 → クリーンビルドが通ることを確認 → 実機でテスト広告がフィルすることを確認 → AdMob 管理画面でリクエストが立つことを確認、までを1ネットワークぶん完了させてから次に進みます。SKAdNetworkItems も同じタイミングで1ネットワークずつ足すと、計測が崩れたときに原因を絞れます。
もう一点、テスト時は必ず AdMob のテストデバイスID を登録してテスト広告で確認してください。本番広告でクリックを繰り返すとポリシー違反になり、せっかくの設定が無駄になります。
参考リンク
メディエーションは設定が通ってしまえば手離れの良い仕組みですが、最初の導入だけは層がいくつも重なっていて詰まりやすい部分です。同じところで止まっている方の切り分けの一助になれば嬉しいです。