XCFrameworksに対応したCarthageを使ってみた


Carthage 0.37.0がリリースされ、XCFrameworksに対応しました🎉
これによりXcode12になってからCarthageで発生していた問題が解消されることになります。ワークアラウンドスクリプトが不要になり、さらにはM1 MacでCarthageが使えるようになります。
なぜCarthageがXCFrameworksを必要としていたかの背景や、XCFrameworksを生成する手順について解説したいと思います。

CarthageでXCFrameworksを生成する手順だけ知りたい方は、 CarthageでXCFrameworksをビルドする へどうぞ。

XCFrameworksって何?

まずXCFrameworksについて説明します。
XCFrameworks自体はXcode11から使用可能になっている技術です。
実際にCarthage0.37.0でビルドしたXCFrameworksを見てみるのが一番わかりやすいです。RxSwiftをビルドした結果が以下です。

.xcframework のディレクトリ下に ios-arm64_armv7ios-arm64_i386_x86_64-simulator というディレクトリがあり、それぞれの中に .framwork ファイルがあるのがわかります。
XCFrameworksを使うことにより、別々のアーキテクチャ向けの複数のframeworkを1つの .xcframework というパッケージで配布することができるようになるのです。

なぜM1ではCarthageが使えなかったの?

M1 MacのアーキテクチャがiOSデバイスと同じarm64というアーキテクチャを採用したことがすべての発端です。

まずIntel MacでCarthageがどのように動作していたかの説明から始めたいと思います。

CarthageがIntel Mac上でどのように動いていたか


Intel MacではXCFrameworks形式を採用しない時もCarthageでビルドしたframeworkを使って、実機でもシミュレータでも動作が可能でした。
これはCarthageでビルドした時に、iOS実機向けのframeworkとiOSシミュレータ向けのframeworkを、 lipo というコマンドでCarthageが1つのframeworkにまとめていたためです。Intel Macはx86_64というアーキテクチャになっているのでMac上で動作するシミュレータもx86_64アーキテクチャになります。iOS実機向けはarm64アーキテクチャです。

ついでにいうとビルド設定のRunScriptフェーズで copy-frameworks というスクリプトを設定していたと思います。これは1つにまとめたframeworkから、再度 lipo コマンドを使って不要なframeworkを除去することをしていました。これにより実機向けのビルドではarm64のフレームワークが残り、シミュレータ向けのビルドではx86_64のframeworkが残るという動きになります。

M1 Macだとどうなるか


M1 Macに対応したXcode12からはこのシミュレータ向けのアーキテクチャにarm64が加わることになりました。M1 Macがarm64アーキテクチャだからシミュレータもarm64になるということです。これによってCarthageは「iOS実機(arm64)」と「M1 Macシミュレータ(arm64)」を1つのframeworkにまとめないといけなくなってしまったのです。ここで問題になるのがiOS実機もM1 Macシミュレータも同じarm64アーキテクチャであることでした。 lipo コマンドでは、同じアーキテクチャ向けのframeworkを1つのframeworkにまとめることはできません。このためCarthageは複数のframeworkを共存させることができるXCFrameworksに対応するよう、方向転換をすることが必要になったのです。

このあたり、ワークアラウンドスクリプトの話なども含めて、拙訳ですが [日本語訳] Carthage issues, Xcode 12, XCFrameworks, Apple Silicon, etc. という記事に詳しく書いてます。興味があれば一読頂けると嬉しいです。

XCFrameworks対応版はM1向けってこと?

そんなことはありません。
XCFrameworks自体はIntel Macでも使用できますし、何よりフレームワークを1つにまとめ、 copy-frameworks で不要なframeworkを除去するという余分な処理が不要になるので、Carthageでのビルド時間およびXcodeでのビルド時間をより短くできます。
設定も単純になるので、M1 Macでなくても今後はXCFrameworks形式にしていく方がメリットが大きいと思います。

CarthageでXCFrameworksをビルドする

ではXCFrameworksを使ってCarthageビルドする手順を以下に示します。
公式にしっかり書かれているので特に言うこともないのですが、以下の3ステップでXCFrameworks形式でビルドができます。

  1. まずCartfileを作ります。これは今までと変わりありません。
  2. 次に --use-xcframeworks オプションつきでビルドします。
    • carthage update --platform ios --use-xcframeworks などになります。
    • これにより Carthage/Build 以下に .xcframework が生成されます。
  3. ビルド設定の Frameworks, Libraries, and Embedded Content セクションに生成された .xcframework を登録します。

これだけです。
これまではBuild Phase設定の Run Scriptcopy-frameworks を動かしていたと思いますが、XCFrameworks対応後は不要になります。既存のプロジェクトでXCFrameworksを使うように移行する場合は copy-frameworks のステップを削除することを忘れないようにしましょう。

XcodeGenでの設定

XcodeGenでXCFrameworksを使う方法についても紹介します。これまでXcodeGenではCarthageで生成したライブラリの設定を簡単にする carthage: というショートハンドが用意されていましたが、XCFrameworks形式ではこれは不要になります。単純に framework: 設定で .xcframework を指定すればOKです。

project.yml
targets:
  SomeProject:
    dependencies:
      - framework: "Carthage/Build/RxSwift.xcframework"
      - framework: "Carthage/Build/RxCocoa.xcframework"
      - framework: "Carthage/Build/RxRelay.xcframework"

まとめ

Xcode12になってからワークアラウンドスクリプトが必要になったり、Carthageはこれからどうなるんだろう、と不安に思っていたのですが、XCFrameworksに対応したことで今後も活躍できる見通しがたったのではないでしょうか。
SPMという強力なライバルがいますが、ビルド時間短縮に繋がるCarthageにはこれからも頑張っていって欲しいです。

参考