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

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

CarthageでXCFrameworksを生成する手順だけ知りたい方は、 [CarthageでXCFrameworksをビルドする](#carthageでxcframeworksをビルドする) へどうぞ。

## XCFrameworksって何？

まずXCFrameworksについて説明します。
XCFrameworks自体はXcode11から使用可能になっている技術です。
実際にCarthage0.37.0でビルドしたXCFrameworksを見てみるのが一番わかりやすいです。RxSwiftをビルドした結果が以下です。
![スクリーンショット 2021-02-07 18.04.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36771/224f55c9-f507-805f-bb52-3205031fff50.png)
`.xcframework` のディレクトリ下に `ios-arm64_armv7` と `ios-arm64_i386_x86_64-simulator` というディレクトリがあり、それぞれの中に `.framwork` ファイルがあるのがわかります。
XCFrameworksを使うことにより、別々のアーキテクチャ向けの複数のframeworkを1つの `.xcframework` というパッケージで配布することができるようになるのです。

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

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

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

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

![Carthage_IntelMac.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36771/fc1161fb-d4ff-1f15-5666-425e3f34b9db.jpeg)
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だとどうなるか

![Carthage_M1Mac.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36771/b4b9b80a-14d5-80bc-64e9-47e79df98876.jpeg)
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_XCFrameworks.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36771/272cbb21-ba79-9ade-513f-b9559309f035.jpeg)

このあたり、ワークアラウンドスクリプトの話なども含めて、拙訳ですが [[日本語訳] Carthage issues, Xcode 12, XCFrameworks, Apple Silicon, etc.](https://zeero.medium.com/%E6%97%A5%E6%9C%AC%E8%AA%9E%E8%A8%B3-carthage-issues-xcode-12-xcframeworks-apple-silicon-etc-fa1932769ad3) という記事に詳しく書いてます。興味があれば一読頂けると嬉しいです。

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

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

## CarthageでXCFrameworksをビルドする

ではXCFrameworksを使ってCarthageビルドする手順を以下に示します。
[公式](https://github.com/Carthage/Carthage#building-platform-independent-xcframeworks-xcode-12-and-above)にしっかり書かれているので特に言うこともないのですが、以下の３ステップでXCFrameworks形式でビルドができます。

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

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

### XcodeGenでの設定

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

```yaml: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にはこれからも頑張っていって欲しいです。

## 参考

* [Carthage issues, Xcode 12, XCFrameworks, Apple Silicon, etc. | by Quentin Fasquel | Medium](https://medium.com/@quentinfasquel/carthage-issues-xcode-12-xcframeworks-apple-silicon-etc-1c60d8635dbc)
  * [[日本語訳] Carthage issues, Xcode 12, XCFrameworks, Apple Silicon, etc. | by zeero | Jan, 2021 | Medium](https://zeero.medium.com/%E6%97%A5%E6%9C%AC%E8%AA%9E%E8%A8%B3-carthage-issues-xcode-12-xcframeworks-apple-silicon-etc-fa1932769ad3)
* [How to declare a Carthage built XCFramework dependency? · Issue #1006 · yonaskolb/XcodeGen](https://github.com/yonaskolb/XcodeGen/issues/1006)