朝、Xcode を開くたびに pod install の解決待ちで数十秒持っていかれる。この小さな摩擦が積み重なって、ある週末についに腰を上げました。複数の壁紙アプリと癒し系アプリを2014年から個人で運用していると、同じ Firebase 構成を何本ものプロジェクトで使い回しています。CocoaPods から Swift Package Manager(以下 SPM)への移行は「いつかやる」の筆頭に居座り続けていた課題でした。
今回はその移行を、Gemini 3 Pro を相棒にしながら3週間かけて進めた記録です。結論から言うと、移行そのものは半日で終わる作業ではありませんでしたが、Gemini を「設計の壁打ち相手」として使うと、ひとりで詰まりがちな箇所をかなり減らせました。一方で、最後に効いたのは AI ではなく、リンカエラーを一行ずつ読む地道さでした。その両面を正直に書き残します。
なぜ今、CocoaPods をやめたかったのか
CocoaPods が悪いわけではありません。長く iOS 開発を支えてきた立派なツールです。それでも個人開発の現場でやめたくなった理由は、はっきりしていました。
ひとつは Podfile.lock と Pods/ ディレクトリがビルド環境ごとに微妙な差異を生み、CI で再現しないエラーに何度か泣かされたこと。もうひとつは、Firebase 公式が SPM を第一級の配布手段として扱うようになり、CocoaPods 側の更新がわずかに遅れる場面が増えてきたことです。Xcode に統合された SPM なら、依存解決の状態がプロジェクトファイルの中で完結します。アプリを長く保守する立場だと、この「外部ディレクトリを持たない」感覚は思った以上に安心感がありました。
両家の祖父がともに宮大工だったせいか、私はものを組み上げるときに「あとから手を入れやすいか」をつい気にします。依存管理も同じで、何年も保守する前提なら、構造はできるだけ素直にしておきたい。SPM への移行は、その素直さを取り戻す作業でもありました。
Gemini 3 Pro に最初にやってもらったこと
いきなり手を動かさず、まず現状の Podfile を Gemini 3 Pro に丸ごと渡して、SPM の各プロダクトへの対応表を作ってもらいました。これが想像以上に効きました。
(プロンプトの要点)
この Podfile を Swift Package Manager に移行したい。
- 各 pod が firebase-ios-sdk のどの product に対応するか表にして
- use_frameworks! を使っている前提で、static / dynamic の注意点も挙げて
- 移行後に不要になる設定(Pods-*.xcconfig 等)も列挙してGemini が返してきたのは、Firebase/Core → FirebaseCore、Firebase/Analytics → FirebaseAnalytics、Firebase/Crashlytics → FirebaseCrashlytics といった対応表でした。地味ですが、pod 名とプロダクト名は一対一で一致しないものがあり、手で調べると地味に時間を食う部分です。ここを最初に俯瞰できたことで、移行のゴール像が一気に明確になりました。
公式ドキュメントにも対応関係は載っていますが、自分の Podfile という具体的な入力に対して「あなたの構成だとこの product を選ぶ」と返してくれる点が、汎用ドキュメントとの違いでした。AI を使う価値は、まさにこの「自分の文脈に翻訳してくれる」ところにあると感じています。
実際の移行手順
対応表ができたら、作業自体は淡々としています。
まず Xcode で File > Add Package Dependencies から https://github.com/firebase/firebase-ios-sdk を追加し、必要なプロダクトだけをターゲットに紐付けます。私のアプリでは FirebaseAnalytics と FirebaseCrashlytics、それに FirebaseRemoteConfig の3つでした。広告まわりは別 SDK なので、ここでは Firebase に絞ります。
次に CocoaPods 側を片付けます。
# CocoaPods の痕跡を消す
pod deintegrate
rm -f Podfile Podfile.lock
rm -rf Pods/
# .xcworkspace ではなく .xcodeproj を直接開く運用に戻すpod deintegrate は Build Settings に書き込まれた Pods-*.xcconfig の参照を外してくれますが、完全ではありません。OTHER_LDFLAGS や FRAMEWORK_SEARCH_PATHS に CocoaPods 由来の値が残っていないか、ここは目で確認したほうが安全です。私の場合、$(inherited) だけ残してあとは消す、という形に落ち着きました。
Crashlytics を使っている場合、dSYM をアップロードする Run Script の中身も変わります。CocoaPods 時代の "${PODS_ROOT}/FirebaseCrashlytics/run" というパスはもう存在しないので、SPM が展開する場所を参照するスクリプトに差し替えます。ここは公式の案内どおりで問題ありませんでした。
いちばん時間を溶かした壁
順調に見えた移行で、最後にぶつかったのがリンカエラーでした。ビルドは通るのに、実機起動時に dyld: Library not loaded 系のエラーで落ちる。Gemini に状況を伝えると、いくつか候補を挙げてくれましたが、決め手にはなりませんでした。
結局、原因は use_frameworks! :linkage => :static を前提に組んでいた既存設定の名残で、SPM 側のリンク方式と噛み合っていなかったことでした。これに気づけたのは、AI の助言よりも、Xcode のビルドログを一行ずつ追って、どのフレームワークが二重に取り込まれているかを地道に突き止めたからです。
この経験は良い戒めになりました。AI は「ありえる原因の地図」を素早く広げてくれますが、その地図のどこに自分がいるのかを確かめるのは、結局こちらの手作業です。Gemini が出した候補リストは、調査の出発点としては確かに有用でした。ただ、最後の一歩は読む・試す・また読むの繰り返しでしか埋まらない。当たり前のことですが、移行のような実務でこそ痛感します。
3週間運用してみての所感
移行後、3週間ほど複数アプリのうち1本を SPM 構成で本番運用してみました。
体感で最も変わったのは、クリーンビルドからの立ち上がりです。Pods/ の再生成がなくなった分、新しい Mac で環境を作り直すときの手数が明確に減りました。累計5,000万ダウンロードといっても運用しているのは私ひとりなので、この「環境を作り直すコスト」が下がるのは、地味ですが日々の精神的な余裕に直結します。
一方で、SPM がすべてにおいて優れているとは言いません。複数のバージョンを固定しつつ細かく制御したい場面では、CocoaPods の Podfile の素直さが恋しくなる瞬間もありました。SPM の依存解決はときどき「なぜそのバージョンを選んだのか」が見えにくく、Package.resolved を読み解く必要があります。万能の正解ではなく、自分のプロジェクトの性質に合うかどうかで選ぶものだと、改めて思いました。
Gemini 3 Pro の使いどころも同じです。対応表づくりや設定の棚卸しのような「広く浅く確認したい」作業では大きく時間を節約できました。逆に、自分のプロジェクト固有の歴史的経緯が絡む不具合では、AI はあくまで補助線でした。この線引きを掴めたことが、3週間でいちばんの収穫だったかもしれません。
次に同じ移行をするなら、私はまず Podfile を Gemini に渡して対応表を作り、pod deintegrate の後に Build Settings を必ず目視する、という順番で進めます。もし今あなたが同じ移行を迷っているなら、いきなり全アプリではなく、影響範囲の小さい1本から試してみることをおすすめします。最後までお読みいただき、ありがとうございました。