長く運用しているアプリほど、ビルド環境の「古い前提」が静かに重荷になっていきます。私の場合は Firebase でした。2014 年から個人で iOS アプリを作り続けてきて、累計のダウンロードは 5,000 万を超えました。その多くで Firebase を使っているのですが、導入当時の自然な選択だった CocoaPods が、Apple Silicon 移行後のビルド時間やプライバシーマニフェスト対応のたびに、少しずつ手間の発生源になっていたのです。
今回、複数あるアプリのうちまず 1 本を Swift Package Manager(以下 SPM)へ移しました。作業そのものより、「どこを Claude Code に任せ、どこを自分で決めたか」の線引きが実用的だったので、その記録を残します。
なぜ今 SPM に移すのか
正直に言うと、CocoaPods でも動いてはいました。動いているものを触るのは怖いものです。それでも移行に踏み切った理由は三つあります。
一つ目は、クリーンビルドの待ち時間です。pod install を含むフローは、CI でも手元でも地味に時間を食います。二つ目は、Xcode 15 以降で Firebase の SPM 配布が十分に安定してきたこと。以前は SPM 版で詰まる話をよく聞きましたが、今は素直に入ります。三つ目は、プライバシーマニフェスト対応です。Firebase SDK 側がマニフェストを同梱するようになり、SPM 経由のほうが取り込みの見通しが良いと感じました。
逆に言えば、これらに困っていないアプリは無理に移す必要はありません。「動いているものを尊重しつつ、痛みが出た順に直す」のが、個人開発で長く続けるコツだと考えています。
実際にやった手順
移行はおおまかに「CocoaPods を抜く」「SPM で入れ直す」「ビルドフェーズを直す」の三段階でした。
最初に Podfile から Firebase 関連を取り除きます。
# Before(Podfile)
target 'MyWallpaperApp' do
use_frameworks!
pod 'Firebase/Analytics'
pod 'Firebase/Crashlytics'
pod 'Firebase/Messaging'
pod 'SomeOtherLib' # Firebase 以外はそのまま残す
end
# After(Podfile)— Firebase の 3 行を削除
target 'MyWallpaperApp' do
use_frameworks!
pod 'SomeOtherLib'
endFirebase 以外にも Pod を使っている場合は、Podfile を空にせず、Firebase の行だけを消すのが安全です。その後、CocoaPods の痕跡を整理します。
# Firebase の行を消したあとに反映
pod install
# Firebase 以外の Pod も無い場合のみ、完全に外す
# pod deintegrate
# rm Podfile Podfile.lock
# rm -rf Pods
# 以降は .xcworkspace ではなく .xcodeproj を開く次に Xcode で SPM の Firebase を追加します。File → Add Package Dependencies から firebase-ios-sdk を指定し、必要なプロダクト(私の場合は FirebaseAnalytics、FirebaseCrashlytics、FirebaseMessaging)だけをターゲットに紐づけます。ここで欲張って全部入れないことが、後のビルド時間に効いてきます。GoogleService-Info.plist はそのまま使えるので差し替え不要です。
ここまでで、Claude Code に渡したのは「移行前後の Podfile の差分」と「project.pbxproj の変更箇所」でした。人間の目だと見落としがちな、CocoaPods 時代に残った参照(Pods-*.xcconfig への Base Configuration 参照など)を機械的に洗い出してもらうのが、いちばん助かった使い方です。
いちばんつまずいた Crashlytics の dSYM
順調に見えた移行で、最後に足を取られたのが Crashlytics の dSYM アップロードでした。CocoaPods 時代の Run Script は、こうした ${PODS_ROOT} 前提のパスで動いていました。
# 壊れる例(CocoaPods 時代の名残)
"${PODS_ROOT}/FirebaseCrashlytics/run"SPM には Pods ディレクトリが存在しないので、当然このパスは解決できず、ビルドは通るのにシンボル化されたクラッシュレポートが届かなくなります。気づきにくい類のエラーです。
正しくは、Build Phases に新しい Run Script を一つ追加し、SPM が配置したパッケージ内の run スクリプトを呼び出します。あわせて Input Files に dSYM と Info.plist のパスを明示します。
# SPM 移行後の Run Script(Build Phases に追加)
"${BUILD_DIR%/Build/*}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run"# 同じ Run Script の Input Files に追加
${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}
$(SRCROOT)/$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)このパスにたどり着くまで、私はビルドログをそのまま Claude Code に貼りました。「dSYM が見つからない」「run スクリプトの場所が違う」という二つの症状を切り分けてもらえたので、当てずっぽうのパス探しを避けられたのが大きかったです。エラーメッセージを丸ごと渡して原因の仮説を立てさせる、という使い方は、こうした環境依存のトラブルで特に相性が良いと感じます。
任せたこと、自分で決めたこと
今回いちばん腑に落ちたのは、Claude Code との「役割分担」でした。
機械的な作業、たとえば残った参照の洗い出し、ビルドログからの原因切り分け、Run Script の雛形作成は、任せたほうが速くて正確でした。一方で、「どのアプリから移すか」「どこまで一度に変えるか」「いま移すべきか先送りすべきか」という判断は、最後まで自分の仕事として残りました。
祖父はふたりとも宮大工でした。手を動かすことそのものが一種の信心だ、という感覚が私の根っこにあります。便利な道具が増えても、どこに手を入れるかを決める責任は自分が引き受ける——その線引きさえ崩さなければ、AI を相棒にした移行作業はとても心強いものになります。作業の速さと、判断の重さは、分けて考えるのが健全だと考えています。
次に取り組むこと
まず 1 本で型ができたので、残りのアプリにも順に同じ手順を展開していきます。次の検証テーマは、プライバシーマニフェストが SPM 経由でどこまで自動的に取り込まれるか、そして Package.resolved でのバージョン固定をリリース運用にどう組み込むか、の二点です。
同じように「動いているけれど、そろそろ移したい」と感じている方の参考になれば幸いです。最後までお付き合いいただき、ありがとうございました。