文字っぽいの。

文字を書いています。写真も混ざります。

Xcode12時代のライブラリ管理ツール選定

背景

iOSDC 2020でMint及びiOSアプリ開発時のライブラリ管理について執筆し、同様の内容をZennでも本として販売した。

zenn.dev

しかし、Xcode12の正式版がリリース後に、執筆時からライブラリ管理を取り巻く状況が大きく変わってきたため、ここに筆を執った次第である。

はじめに

この記事はXcode12を利用してiOSアプリ開発を行う前提で執筆されている。また、情報は2020年12月25日現在のものとなる。

iOSアプリ開発を行う場合、ライブラリ管理には

  • CocoaPods
  • Carthage
  • Swift Package Manager(以下 SwiftPM)
  • Manual

が選択肢として挙げられる。Manualは管理といえるのか怪しいため今回は省略する。なお筆者は、

  • 本業:Carthage, CocoaPods, Mint 1
  • 副業:SwiftPM, Mint
  • 個人開発1:SwiftPM, Mint
  • 個人開発2:CocoaPods, cocoapods-binary

という構成で開発を行っている。

以下、CocoaPods、Carthage、SwiftPMのそれぞれでライブラリ管理をする場合のメリットとデメリットを述べる。

CocoaPods

■メリット

CocoaPodsは2011年に作られたツールで、すでに9年以上の歴史がある。Objective-CからSwiftへの移行も乗り越えた歴戦の戦士であり、十分に長い年月利用されているため、とてもよく枯れている。

また、CocoaPodsにはPluginの仕組みがあり、便利なサブコマンドを追加することができる。有名なところではcocoapods-keyscocoapods-binaryがある。これらPluginもGemとして提供されているので、Gemfile に追記して bundle install して Podfile に追記するだけで利用することができる。

■デメリット

Ruby Gemなので、Rubyが必要になる。Rubyは別に嫌いではないし、個人OSSでも利用している。ただ、こちらとしてはiOSアプリ開発をしたいのであって、Rubyでコードを書くわけではない。SwiftでiOSアプリを書きたいだけなのに、どうしてrbenvでRubyを入れ、Bundlerを入れ、Gemfileを書いてCocoaPodsを入れてバージョン管理をしなければならないのだろうか。Fastlaneも同様にGemなので許容範囲とも考えられるが、正直管理するものが多くて面倒である。

[追記 2020/12/25 15:45]

今ではSystemのRubyを利用することも可能なようです。

[追記ここまで]

よく知られたデメリットとして、Clean Buildするとライブラリも再ビルドが必要になるためビルド時間が長くなる点がある。これはCocoaPodsの仕組み上なくなることは無いが、最近ではそこまで気にしなくても良いだろう。Swift2や3系の頃のSwiftコンパイラはかなり安全側に倒してあり、差分ビルドよりフルビルドが選ばれがちであった。そのため、1行変更しただけでフルビルドが走り、その度にCocosPodsで導入したライブラリ群もビルドされるため、長時間のビルドを待つ事態が頻発していた。Swift5系の今では、こういった挙動は皆無ではないにせよかなり減った。

SwiftUIで開発していると稀に「Derived Dataを消してXcodeを再起動すると直るエラー」に出会う。これが何を起因として発生しているのか不明なのでなんとも言えず、Xcodeのアップデートによって発生しなくなるかもしれないが、もし頻発するようであればフルビルドによって開発速度が低下することは記しておく。

Carthage

■メリット

導入には.pkg, Homebrew, Mintなどが使える。別のプログラミング言語を準備すること無く導入が可能である。

Carthageで導入されたライブラリは事前にビルドされており、Xcode上からはビルド済みのライブラリを利用するため、Clean Buildをしてもライブラリ群の再ビルドが走らない。これによって、CocoaPodsに比べてビルド時間を短縮することができる。ちなみに、cocoapods-binaryでも同様の状況にはできる。

■デメリット

Mintを利用せず .pkg やHomebrewで導入した場合、Carthage自体のバージョン管理を行うことができない。そのため、複数人で開発をしている場合に、チームメンバーそれぞれの手元で異なるバージョンのCarthageが動くことになる。これはMintを導入することで解決可能なため、些末な問題だとも言える。

ライブラリ群が事前にビルドされることはデメリットにもなり得る。開発環境を整える際に大量のライブラリ群のビルドを待たなければならないし、 git switchした際にライブラリのバージョンが異なっている場合にも、ビルドし直さなければならない。これは、Carthage/Build ディレクトリをGitの管理下に含めることで対処可能ではある。しかし、Carthage/Build のサイズが大きいためGitリポジトリの肥大化が加速してしまい、別の問題に直面することになる。

さて、ここまで色々と書いてきたが最も大きなデメリットは、Xcode12からCarthageがまともに使えなくなってしまったことだ。この問題は公式から案内されているWorkaroundを利用することで対応が可能ではある。

しかし、2020年9月16日にXcode12 GMが提供されてから3ヶ月が経った今でもこの問題は解決されていないため、対応を待ち続けるのは良策ではないだろう。

このWorkaround問題に関係しているが、未だにCarthageはXCFrameworksに対応できていない点もデメリットの一つである。XCFrameworksはWWDC2019で発表された仕組みであり、当時は「これをCarthageがサポートしたら copy-frameworksが不要になる!」と言われていた。優秀なOSSコミッター各位が揃っている中で未だに対応していないということは、非常に大きく困難な壁があることは想像に難くない。なお、CocoaPodsとSwiftPMはXCFrameworksに対応している。

SwiftPM

■メリット

Xcodeをインストールすればすぐに使える。サードパーティツールを導入する必要が無く、Xcodeを起動して待っているだけでコードを書き始められる点が最大のメリットだろう。CLIツールを利用しなければ、セットアップスクリプトも不要になる。

iOSアプリを開発をしたいのにRubyやHomebrewを利用してサードパーティツールを入れ、そのツールをセットアップするために、setup.shMakefileに細々と設定を書く必要がなくなるのは非常に心地が良い。README.md# SETUP 項には

$ xed .

とだけ書けば良い。

■デメリット

SwiftPMではXcodeでProjectが開かれると、管理しているライブラリ群の更新を確認する。これはAndroidのGradleに似た挙動ではあるが、地味に時間がかかるため若干不便である。もちろん利用するライブラリが増えると、この待ち時間は長くなっていく。

もっとも不便さを感じるのは、SwiftPMではライブラリをStatic Frameworkとして扱う点である。このため、例えばライブラリAがアプリ本体とWidgetKitの両方で使われていると、エラーになってビルドができない。この問題を解決するためには、1度自作のFrameworkに包んでDynamic Frameworkの様に振る舞わせる必要がある。

この問題は、Embedded Frameworkでも発生する。なお、筆者は試していないがEmbedded Frameworkをprojectとして管理し、それぞれのproject毎にSwiftPMの設定をすることで回避することが可能かもしれない。

Firebase iOS SDKやRxSwiftを利用した際にも問題がある。Firebase iOS SDKのSwiftPM対応はまだbetaであるため、Workaroundを利用しないとAppStore Connectへのバイナリアップロードができない。

If you're using FirebaseAnalytics, Xcode 12.0, and have an issue with device installation or archive uploading, see the workaround at https://github.com/firebase/firebase-ios-sdk/issues/6472#issuecomment-694449182.

RxSwiftの場合はRxTestを利用している場合にリンクエラーになるバグがある。

[SR-12303] Swift Package Manager package stopped working after Xcode 11.3, Xcode 12, etc. - Swift

これからアプリを書き始めるならCombineを使う手もあるが、すでにRxSwiftとRxTestが導入されているプロジェクトで、Combine移行するコストは無視できない大きさである。

結論

2020年12月25日現在では、CocoaPodsを選択するのがベターだと言える。

しかし、今後CarthageのXCFrameworksサポートや、Firebase iOS SDKのSwiftPM/Carthageサポート完了によって最適解は都度変化するだろう。もし記事公開から数カ月後にこの記事を読んでいる読者がいたら、最新の状況を調べてから判断して欲しい。

CLIツールの管理

話が脇道にそれるが、Swift製のCLIツールのバージョン管理についても書く。CLIツールとしてよく使われるものには、

  • realm/SwiftLint
  • nicklockwood/SwiftFormat
  • mono0926/LicensePlist
  • mac-cain13/R.swift
  • SwiftGen/SwiftGen
  • yonaskolb/XcodeGen

などがある。

まず、Homebrewによるバージョン管理は困難2なため除外する。そのため選択肢としては、SwiftPM, Mint, CocoaPods,cocoapods-binaryがある。

SwiftPMの場合、すでにアプリ側でSwiftPMを利用している場合にダミーファイルを用意する必要があるため若干煩雑である。よって現時点では、Mint, CocoaPods, cocoapods-binaryのいずれかを用いるのがよいだろう。もし、アプリ側のライブラリ管理をCocoaPodsに一元化するのであれば、cocoapods-binaryを利用するのがベターだと考える。

実際にこれらツールを用いてCLIツールを管理する方法については、上述したZennの本でも記載している。ただし、わざわざ購入しなくても検索したほうが無料で最新の情報が得られるだろう。

zenn.dev

感想

私個人としてはCarthage+Mintによって「最初にビルドをしておけば、後は高速にコードを書き続けられる」という状況が得られると考えていたが、昨今の状況を鑑みると難しそうである。


  1. CocoaPodsを引き剥がそうと思いながら作業をしていたら、Xcode12リリース後に状況が一変してしまって厳しい。

  2. HomebrewでもFormula提供者が特別な対応をすることでバージョンを指定した(かのような)インストールが可能だが、全ての過去バージョンがインストールできるかはFormula提供者が対応するかどうかに依存する。