CocoaPods から Swift Package Manager に移行した話

こんにちは、モバイルエンジニアの中村(@Kyomesuke)です。

私が担当している kintone の iOS アプリでは、脱レガシーを目指して Apple 製の新しいフレームワークやビルドツールの導入/移行に取り組んでいます。 今回はその取り組みの一つである、ライブラリマネージャーを CocoaPods から Swift Package Manager(SPM)に移行した件について紹介します。

CocoaPods から SPM に移行を決めた理由

サイボウズの開発本部では徐々に Apple シリコン搭載の MacBook が普及しており、ビルド速度の面での開発環境改善が進んでいます。 ただし、ライブラリマネージャーとして CocoaPods を利用しているプロジェクトの場合、環境構築が困難であり新規メンバー参入の障害となっていました。 これは、CocoaPods が Ruby 製であることに起因しています(Ruby は現状そのバージョンによっては Apple シリコン搭載 Mac でうまく機能しないため、Mac に合わせて調整や対応が必要です)。 そこで、最新の Xcode さえインストールされていれば、GitHub リポジトリを clone するだけで開発環境の構築が完了する状態にしたかったのです。

SPM のメリット

  • SPM はプラットフォーマーの Apple が提供するもののため、長期的な保守運用性が高い。
  • 今後 SPM のエコシステムが充実してくる見込みがある。
    • Apple 公式のライブラリは基本的に SPM でしか配布されない。
  • App Store Connect へのアーカイブアップロードのために使用している Fastlane 以外で Ruby に頼らなくなるため、基本的には CI 以外で Ruby を必要としない。
  • 比較的新しい技術を使っていることで採用にも好影響になる。

SPM の懸念点

  • 現状は Swift Package plugins に公式対応している開発ツールは少ないが、今後拡充されるはず。
  • SPM と CocoaPods を併用することは可能だが、ライブラリ管理のルートが複数あるのは混乱を招く。

SPM のデメリット

  • Xcode でプロジェクトを開いたときに Resolving Package Graph が自動で走るため、それが終わるまでは作業ができない。

上記のメリットやデメリットを踏まえて、将来的な開発生産性とメンテナンス性が高いと判断し、CocoaPods から SPM への移行を決めました。

SPM への移行に伴って行ったこと

CocoaPods から SPM への移行に伴って行ったことについて紹介します。

社内フレームワークの SPM 移行

サイボウズでは複数のモバイルアプリをリリースしており、それらに共通しているログインを司る機能をフレームワークとして切り出し、製品を横断して利用しています。 iOS 製品向けの社内フレームワークはこれまで CocoaPods で社内配信して利用していたため、これも SPM に移行が必要でした。

ビルドツールを SPM の command plugin に置き換え

kintone の iOS アプリでは、リソースを Swift のコードから簡単にアクセス可能にするためにR.swiftを使って Swift のコードを自動生成しています。 これまでは、CocoaPods で R.swift をインストールして Build Phases の Run Script からコード生成のコマンドを実行していたのですが、SPM への移行に伴い、command plugin から実行するように置き換えました。 ただし、現状 R.swift の運用チームから command plugin が提供されていないため、個人で Fork して command plugin 対応している方のリポジトリのもの(quentinfasquel/R.swift)を利用しています。

プロジェクトの作り直し

CocoaPods を利用する場合はプロジェクト形式が .xcodeproj から .xcworkspace になりますが、SPM のみを利用する場合は .xcodeproj で良いため、この機にプロジェクトを作り直しました。

しかし、結果としてデグレが起きてしまい、試験でそれが発覚しました。 新しいバージョンの Xcode でプロジェクトを作り直すとデフォルトでの Info.plist の扱いが変わることが原因でした。 Build Settings の Generate Info.plist File という項目が Yes に指定されることで、今まで Info.plist で指定していた内容の一部が .pbxproj ファイルで管理されるようになり Info.plist から自動的に削除されるようになりました(例えば、Current Project VersionMarketing Version、カメラやマイクなどの権限の Usage Description が .pbxproj で管理されるようになります)。 これによって、Info.plistで環境変数が指定されていること前提で組まれている CI のリリースワークフローが壊れたり、権限周りの動作が壊れてしまっていました。 これらは CI での環境変数の指定のタイミングを調節したり、InfoPlist.strings ファイルを追加してローカライズされた文言を追加することで対応できました。

SPM 対応していないライブラリへの特別対応

kintone の iOS アプリは、画面遷移のルーティングをDeepLinkKitというライブラリを用いて実装しています。この DeepLinkKit は Objective-C で書かれたライブラリなのですが、最終リリースが 2019 年で止まっており、SPM 対応の Pull Request もマージされる気配がなかったため、必要な機能部分だけを Swift で作り直しました。 作り直しに関しては、NSDictionary と Swift の Dictionary の仕様の違い(参照渡し/値渡し周り)や、正規表現でのマッチにおいて NSString と Swift の String の文字の数え方の違いに苦労しました。

また、アプリが使用しているライブラリを一覧表示する機能があり、AcknowList という CocoaPods を前提に動くライブラリを使用していたのですが、SPM への移行でこれが使えなくなったため、新たに LicenseList というライブラリを Swift Package として作って置き換えました。 詳しくは以下の記事をご覧ください。

blog.cybozu.io

Swift Package ライブラリのアップデート情報を自動検知する仕組みづくり

GitHub には Dependabot という仕組みがあり、 Gemfilepackage.json など言語が持つパッケージ管理のマニフェストファイルを見て、古かったり安全でないライブラリを自動で検出してくれますよね。 しかし、Dependabot は SPM の Package.swiftPackage.resolved には対応していません。 そこで、Dependabot のようにプロジェクトで使用している Swift Package ライブラリのアップデート情報を自動検知して、リポジトリに Pull Request を立ててくれる仕組みを作りました。 現状、社内専用の仕組みとして安定稼働するか確認していますが、折を見て OSS にできないか検討したいと思っております!

CI/CD のワークフローの修正

ライブラリマネージャーが変わったことで、CI/CD のワークフローにも修正が必要になりました。 コマンドラインによるビルドやテストの修正、CocoaPods のライブラリのキャッシュの仕組み削除、Ruby 関連のセットアップの削除などを行いました。 また、ワークフローの修正において注意すべき点がありました。 kintone モバイルの CI/CD には GitHub Actions を用いているのですが、ローカルマシンと CI のマシン(macOS runner)の Xcode のバージョンの違いにより swift-tools-version が異なり、Package.swift で使える PackageDescription の構文や機能が変わります。 そのため、ローカルマシンと CI のマシンで swift-tools-verison が揃うように Xcode のバージョンを調整する必要がありました。

おわりに

この記事では、kintone モバイル iOS で利用しているライブラリマネージャーを CocoaPods から Swift Package Manager に移行した件について紹介しました。 今後も新しい技術を取り入れていき、より開発モチベーションと保守運用性が上がるようなプロダクトにしていくため、脱レガシーを推進していく予定です。 ユーザーも開発者も #体験が良いプロダクト に改善していきたいと考えています!