Xcode 12がリリースされて以来、それは私たちiOS開発者コミュニティにいくつかのトラブルをもたらしました。少なくとも carthage を1つ以上のプロジェクトで使用している人たちにとっては…

数ヶ月間 Xcode 12ベータを使用している場合、発生している問題とワークアラウンドの存在を既に知っているでしょう。今日(訳者注:2020年9月26日)でリリースから約2週間が経過しましたが、なぜすでに確定的な修正がないのか不思議に思うかもしれません。この記事では、この問題についての私の理解と、それが何に起因しているのかを共有します。

詳細に深く入る前に、 carthage が何をしているか、それがどのように動作するかを理解することは重要です。一部の人にとってはそれは自明であるかもしれませんが、他の人は手がかりを持っていないかもしれません。

1 — carthageがどのように動作するのか?

実は非常にシンプルです。iPhone Simulator上でアプリを実行するかiPhone実機でアプリを実行するかを切り替えるたびにコマンドラインを行き来したくないので、Carthage は各依存ライブラリの fat framework を構築します。これにより、両方のプラットフォームをシームレスに切り替えることができます。アプリはコンパイル済みのframeworkとリンクするだけです。

fat frameworkとは、複数のバイナリをマージしたバイナリをもつframeworkのことです。iOSプロジェクトで必要となる依存ライブラリでいうと、 iphonesimulatoriphoneos それぞれをビルドした結果である2つのバイナリを、マージすることになります。

基本的にcarthageは以下の2つのステップを踏んで依存ライブラリを構築します:

  • xcodebuild を使って各プラットフォーム (iphonesimulatoriphoneos) 用にビルド
  • lipo を使って、それらをマージ

そして通常は、あなたのXcodeプロジェクトではCarthageが提供する copy-frameworks スクリプトを実行する Build Script Phase を設定しています。これは、AppStore上で両方のプラットフォーム用のバイナリ配布を避けるためです。スクリプトは不要なバイナリを除去するために再び lipo を使用し、ターゲットとするプラットフォーム用のバイナリだけを保持するようにします。これにより、アプリの embedded framework には fat でないframeworkを組み込むことができます。

2 — Xcode 12 でのCarthageの問題

さよなら VALID_ARCHS、ようこそ EXCLUDED_ARCHS、これがビルド設定におけるXcode12のアップデートです。

これは注目すべき変更である一方、 iphonesimulator 向けにビルドすると、 arm64 用のシンボルが生成されることに気付いたかもしれません。これは大きな変化で、問題を発生させている本当の要因です。

iPhone Simulator上でアプリを実行するということは、Mac上で実行するということであり、これまでの実際の意味はアーキテクチャ i386x86_64 上で実行することでした。
6月に開催されたWWDC 2020で明らかにされたApple Siliconは、Appleが製造する次世代の arm64 プロセッサです。これらのプロセッサはMac用に設計されており、これがあなたのXcode 12が今 iphonesimulator 向けに arm64 シンボルを生成する理由です。

私の理解では、 lipo は同じアーキテクチャの複数のバージョンをマージすることができず、これがCarthageが iphonesimulatoriphoneos をマージしようとしたときに失敗する理由です(双方とも arm64 を含むようになりました)。

以下のスクリプトは、Carthage コミュニティで共有されたワークアラウンドスクリプトです。これは、 iphonesimulator をターゲットにしたときにXcode 12が arm アーキテクチャをビルドしないようにしてくれます。

'EXCLUDED_ARCHS[sdk=iphonesimulator*] = ... ' を使用している別のバージョンのスクリプトがあるかもしれません。

これは長期的な解決策ではないかもしれません

このスクリプトは今のところ正常に動作しますが、我々が次世代の Apple Silicon MacBookでcarthageを使用する時には壊れるかもしれません…
(訳者注:原文が書かれたのは2020年9月26日のため、「次世代の」という表現になっています。本翻訳は2021年1月に書いており、Apple Silicon Mac はすでに現実です。)

あなたのコードに( #if targetEnvironment(simulator) を使用して)シミュレータ固有のコードが含まれている場合、 arm64 アーキテクチャを除外しても十分ではないでしょう。

これが XCFramework が舞台に登場する理由です…

3 — XCFrameworkは fat framework より優れている

XCFrameworksはXcode 11で導入されたもので、バイナリを配布するためのソリューションとしては、fat frameworkよりも優れています。コンセプトはシンプルで、複数のバイナリを一つにまとめるのではなく、複数のバイナリを配布できる新しいファイル構造をXcodeに導入しようというものです。

XCFramework と fat framework の違いを確認してみましょう。

fat frameworkの例

fat frameworkの場合のファイル構造を以下に示します(Carthageでビルドしています)。

file コマンドを実行して、 MyFramework バイナリに何のアーキテクチャが含まれているかを確認してみましょう…

MyFramework: Mach-O universal binary with 2 architectures:
[x86_64:Mach-O 64-bit dynamically linked shared library x86_64]
[arm64:Mach-O 64-bit dynamically linked shared library arm64]
MyFramework (for architecture x86_64): Mach-O 64-bit dynamically linked shared library x86_64
MyFramework (for architecture arm64): Mach-O 64-bit dynamically linked shared library arm64

XCFrameworkの例

XCFramework のファイル構造は以下のようになっています。

fatバイナリを含む1つだけのframeworkを持つのではなく、特定の命名規則の下に構成された MyFramework.framework の複数のバリエーションが存在しています。これらの各バリエーションは、それぞれのプラットフォームで有効なアーキテクチャを持つfatではないバイナリを含んでいます。

なぜそれが良いのか?

  • マージ&除去が必要ない : XCFrameworkが優れている第一の理由は、高速であるということです。 lipo を使用する場合、2つ(またはそれ以上)のプラットフォームのバイナリをマージし、最終的に1つのプラットフォームのバイナリにするために除去処理が行われます。これは必要ないことのように思えます。 XCFramework を使用する場合、Xcodeはリンクするバイナリがどこにあるかをシンプルに理解しています。
  • プラットフォーム固有のアーキテクチャ : 先程の例では、選択された ios-arm64_x86_64-simulator という名前のフォルダには、iphonesimulator 用にビルドされた MyFramework.framework のバリエーションが含まれています。この名前の付け方は、通常のx86_64(現在のMacBook用)だけでなく、arm64(将来のApple Siliconマシン用)の両方のアーキテクチャがバイナリに含まれることを暗示しています。これはまさに lipo が現在失敗しているところです。XCFramework は、異なるプラットフォームに同じアーキテクチャを配布することを可能にします。つまり、 tvOS, tvOS Simulator, macOS, iOS, iOS Simulator, watchOS など、すべてのターゲットとなるプラットフォーム向けのバイナリを含む、単一の.xcframeworkパッケージを配布することができます。これは lipo では決して可能ではありませんでした。

4 — To be continued

CarthageのXCFrameworkサポート

すべてのソースコードがオープンソースであるとは限らないので、バイナリを配布することは常にベンダーが要求することです。そして、プラットフォーム間での共有アーキテクチャの問題を解決するのは lipo では不可能だと思われるので、Carthageはxcframeworksをサポートする必要があります。幸運なことに、これはCarthageコミュニティで現在進行中の作業です。

XCFrameworkとSPM

XCFrameworksはXcode 11から利用可能であり、Swift Package Managerも同様です。しかし、Xcode 12で導入された swift-tools 5.3 までは、バイナリパッケージは存在していませんでした。これは別の記事で取り上げます。

これはオープンな議論ですので、遠慮なくコメントしてください。
あなたのプロジェクトが Xcode 12 に移行するのに問題がまだありますか?
こちらにも投稿してください。