こんにちは。今年の5月に入社したiOSエンジニアの大石(id:k_oishi)です。 今回は弊社がリリースしているStudyplusのiOSアプリのライブラリ管理をCocoaPodsからCarthageに移行した件をご報告します。
プロジェクトの構成と開発PCのスペック
現在のStudyplus iOSは以下の構成となっています。
- 開発環境: Xcode 10.3
- 言語: Swift, Objective-C(割合は86:14)
- ライブラリ管理: CocoaPods, Carthage
- CI/CD: fastlane, CircleCI, DeployGate
また、私が入社時に希望して用意されたMacBook Proのスペックは以下のとおりです。
気になるライブラリのビルド時間
私が入社した時点でCarthageの導入はされていましたが、Carthageで管理されていたライブラリは一部のみでした。 ほぼ全てのライブラリがCocoaPodsで管理されている状態でした。
そのような状態で開発を進めていましたが、クリーンビルドで5分以上の時間がかかり、ちょっとしたコードの修正やブランチの切り替えの際にライブラリのビルドが走るのは結構つらいものがありました。
そこで私は過去に経験したプロジェクトでもCarthageへの移行の経験がありましたので、チームメンバーと相談してCarthage移行を進めることになりました。
Carthage移行のはじまり
まず、現在CocoaPodsで管理しているライブラリからCarthageに対応しているライブラリを洗い出しました。 以下のライブラリが対応していましたので、1つずつ順次進めて行きました。
- Apollo
- Facebook SDK
- Firebase
- Nuke
- Realm
- SVProgressHUD
特定のライブラリのビルドがうまくいかない件
Carthage移行を進めていく中でビルドエラーに遭遇しました。
このエラーは以下のライブラリの移行中に発生しました。
- Facebook SDK
- Firebase
- Realm
Argument list too long(コマンドの引数が長すぎる)
というエラーのようですが、ライブラリのビルドについてあまり詳しくない人にとって、なぜこれが起きたのか全くわかりません。ただ、なんとなくですが、同じような引数のパスがひたすらループしているように見えました。(このスクリーンショットは一部分で全体はこの数十倍の量)
プロジェクトの設定の変更
プロジェクトの設定をひたすら見直していたところ、FRAMEWORK_SEARCH_PATHS
の$(SRCROOT)
がrecursive
になっているのがビルドエラーの原因かもしれないことがわかりました。まず既存のコードでnon-recursive
に変更してビルドが通るかを確認してから、Carthageの移行を行いビルドできることを確認しました。
(補足) CarthageでのFirebase導入の注意点
Firebaseの各ライブラリをCarthageから導入する場合は以下のようなJSONファイルをCartfile
に指定し、バイナリ形式でダウンロードされる仕組みです。
binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseAnalyticsBinary.json" == 6.7.0 binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseDynamicLinksBinary.json" == 6.7.0 binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseAdMobBinary.json" == 6.7.0 binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseRemoteConfigBinary.json" == 6.7.0
なお、Firebaseの各frameworkはStatic Libraryのため、従来のライブラリに必要なcopy-frameworks
の手順は必要ありません。
また、導入するFirebaseの機能によっては追加で関連するライブラリ等をLink Binary With Libraries
に追加する必要があります。
詳細はこちらのエントリを参考にしてください。 Crashlytics をどうしても Carthage で使いたいあなたへ
(補足) LicensePlistの設定変更
アプリで使用しているライブラリのライセンス情報の表示にLicensePlistを使用している場合、CocoaPods使用時に取得できていたライセンス情報がCarthage移行後に取得出来なくなる場合があります。 今回のケースではJSONファイルに記述されたバイナリを取得しているFirebaseが該当します。
このような場合、Firebaseのライセンス情報を表示するためにlicense_plist.yml
を作成して以下のような記述を追加しました。これでこれまでと同様にライセンス情報が表示されるようになりました。
github: - owner: firebase name: firebase-ios-sdk version: 6.70.0 rename: firebase-ios-sdk: Firebase
以上でローカルの開発PC上のCarthageへの移行が完了しました。
Carthage対応のまとめ
結果、ローカルの開発PCでは以下のようにビルド時間を短縮することができました。 移行前 5分〜6分 → 移行後 1分40秒〜2分
もちろん初回は$ carthage bootstrap~
の実行が必要ですので、その分のビルド時間はかかるのですが、開発の最中にライブラリの再ビルドが走ることはなくなり快適に開発を行えるようになりました。
今回の移行では以下がポイントとなりました。
- 1ずつ移行を試してビルドエラーの原因を特定しやすくする
- ビルドエラーが出る場合はプロジェクトの設定を見直す
- Firebaseの導入は他と少し違うので要チェック
- ライブラリによってはLicensePlistの対応が必要になるかも
CIの改善
CircleCIでのCarthageのキャッシュ利用
ローカルの開発PCでの移行が完了しましたので、CircleCIの設定を変更してCarthageのビルド結果をキャッシュするようにします。 キャッシュの設定をしないと毎回Carthageのビルドが走り、ローカルの開発PCと比べるとCI環境ではとんでもないビルド時間が毎回かかってしまいます。
キャッシュを利用するためにはconfig.yml
に以下のステップを追加します。
setup_carthage: steps: - restore_cache: key: v1-ca-{{ checksum "Cartfile.resolved" }} - run: name: Carthage command: carthage bootstrap --platform iOS --cache-builds --no-use-binaries - save_cache: key: v1-ca-{{ checksum "Cartfile.resolved" }} paths: - Carthage
キャッシュを利用することで初回のみはそれなりの時間がかかりますが、ライブラリに変更がない場合は以下のように数十秒でCarthageの設定を終わらせることが出来るようになりました。
さらなるCIの高速化を求めて…〜CocoaPods〜
Carthage導入後のCIの結果を眺めていたところ、もう少しビルド時間を改善できそうな部分を見つけました。CocoaPodsのステップです。
このステップで時間を要していたのがCircleCIが独自に用意したコマンドです。これはCocoaPodsのリポジトリの取得を従来より早く取得できるというものでした。
curl https://cocoapods-specs.circleci.com/fetch-cocoapods-repo-from-s3.sh | bash -s cf
しかし、CocoaPodsは少し前にリリースされたv1.7からCDNに対応しました。これによりリポジトリを取得する必要がなくライブラリを導入できるようになりました。要するに前述のコマンドを実行する必要が無くなったのです。このコマンドの実行を削除した結果、以下のように当該ステップの時間を短縮することができました。
CI高速化の結果
Carthage移行前は毎回ビルドに20分近くかかっていましたが、Carthage移行後のキャッシュが効いた状態では8分程度でビルドが終わるようになりました。
Carthage移行前
Carthage移行後
いかがでしたか?
今回のCarthageへの移行を行ったことで、開発時とCIのビルド時間を短縮することができました。
ビルドエラーが発生すると解決するのがやや大変ではありますが、ライブラリのより深い世界を知るきっかけにもなるかと思います。 現在もCocoaPodsでライブラリ管理されているプロジェクトもあるかと思いますが、ぜひ試されてはいかがでしょうか。
先日のiOSDC Japan 2019の以下のセッションではCarthageやCocoaPodsで使用されるライブラリやフレームワークに関する解説やビルドエラーの際の原因の切り分け方法がわかりやすくまとまっており、大変参考になる内容でした。
「ライブラリのインポートとリンクの仕組み完全解説」の資料を公開しました。この辺で困ったらいつでも気軽に聞いてください😊 https://t.co/PQOegMl3L6 #iosdc
— kishikawa katsumi (@k_katsumi) September 6, 2019
また、今後ライブラリ管理の選択肢の1つになりそうなSwift Package Manager(SwiftPM)の動向もチェックしたいと思います。