xcframeworkを作成する（第1回）

GMOアドマーケティングのT.Oです。

Xcode11でフレームワークの新しい配布形態としてxcframeworkが導入されました。
xcframeworkを使用すると、iOS実機、iOSシミュレーターなどの複数のプラットフォーム用のフレームワークを1つにまとめることができます。またアプリに組み込む際には、それぞれのプラットフォームを意識することなく利用できるようになりました[^1]。
[^1]: Xcode10までは、各プラットフォーム用のフレームワークを1つのファイルにまとめることはできていたのですが、それをアプリで利用する際に必要なプラットフォームに適合したフレームワークのみを切り出すためのスクリプトを実行する必要があり、手順がやや煩雑になっていました。
ここではSwiftでxcframerworkを作成、利用する手順についてご紹介します。

●前提
作成するxcframeworkは、iOS10以降で利用可能なEmbedded Frameworkをまとめたものとします。
（iOS13, iPadOSにも対応しています）
開発環境としてXcode 11.0を利用しSwift5を使用します。

●xcframeworkの作成手順
1. プロジェクト作成
2. xcframework生成のためのビルド設定
3. Objective-C対応
4. テスト用のアプリ作成

今回は上記の手順のうちの
「1. プロジェクト作成」
「2. xcframework生成のためのビルド設定」
についてまとめます。

# 1.プロジェクト作成
まずフレームワーク用のテンプレートを選択してプロジェクトを作成します。
Xcodeで「File→New→Project..」メニューを選び、テンプレートの選択画面で「Cocoa Touch Framework」を選択します。
<img width="732" alt="xcframework_p1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36602/9996ca92-18fc-32ca-4baf-3ef508edc21b.png">

新規プロジェクトのオプション選択画面が表示されるので、Product Nameなどの設定を行います。
<img width="732" alt="xcframework_p2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36602/8a9da51e-215b-c340-5b25-7ac86798575d.png">
iOS10以降で利用可能としたいので、PROJECTのDeployment→iOS Deployment Targetで"iOS10.0"を選択します。
<img width="870" alt="xcframework_p2-2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36602/ef59f244-7aac-e48c-d06f-0e8cd63168f0.png">
xcframeworkを作成するため、Build Options→Build Libraries for DistributionでYesを選択します。
<img width="767" alt="xcframework_p3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36602/62ab3e7a-42a9-e38d-f109-b7e1e1ce48de.png">
次に「New File」を選択し、テンプレート選択画面で「Cocoa Touch Class」を選び
フレームワークの処理をSwiftで記述します。

ここでは以下のように文字をコンソール出力することにします。

```Ore.swift
import UIKit

public class Ore: NSObject {
    public func oreMethod(){
        print("Hello Ore XCFramework");
    }
}
```

ビルドターゲットをフレームワーク（この例だとOreXCFramework）に変更します。Product→Buildを選択しビルドします。ビルドエラーがないことを確認します。

# 2.xcframeworkのためのビルド設定

iOS実機、iOSシミュレータ用のフレームワークをビルドして1つのxcframeworkにまとめるためのビルド設定を行います。

「File→New→Target」で新規ターゲットのテンプレート選択画面で「Cross-platform→Aggregate」を選びます。
<img width="732" alt="xcframework_p4.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36602/c41d424e-5f84-5ac9-a772-9b8ac9d14ab9.png">
Product Nameを指定します。ここでは例として"Aggregate"と指定します。
<img width="732" alt="xcframework_p5.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36602/ed99add8-d855-c67c-fab5-1bf36752f8e9.png">
次にTARGETSから作成した"Aggregate"を選択した状態で「Build Phases」を選び、「+」を選択して「New Run Script」を選びます。
<img width="870" alt="xcframework_p6.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36602/9f296970-daf8-b7d9-a58e-89acd7096ada.png">
Run Scriptが作成されます。
<img width="870" alt="xcframework_p7.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36602/5b16b746-8f12-6d69-112b-563c474755e9.png">

Run Scriptの"#Type a script or drag file from your workspace to insert its path"と記述されている部分に以下のxcframework出力用スクリプトを貼り付けます。
このスクリプトを実行するとフレームワークのプロジェクト以下にOutputディレクトリを作成します。そしてOutputディレクトリ以下にxcframework作成のための中間ファイル、archiveファイル、xcframeworkを出力します。

```

# 出力先ディレクトリ（プロジェクトの直下）
OUTPUT_DIR=${PROJECT_DIR}/Output

# 中間ファイルの出力先ディレクトリ
DERIVED_DIR=${OUTPUT_DIR}/${CONFIGURATION}-derived

# archiveの出力先ディレクトリ
ARCHIVE_DIR=${OUTPUT_DIR}/${CONFIGURATION}-archive

# xcframeworkの出力先ディレクトリ
XCFRAMEWORK_DIR=${OUTPUT_DIR}/${CONFIGURATION}-xcframework

# 出力先ディレクトリ削除
rm -rf ${OUTPUT_DIR}

# 中間ファイルの出力先ディレクトリ作成
mkdir -p ${DERIVED_DIR}

# アーカイブファイルの出力先ディレクトリ作成
mkdir -p ${ARCHIVE_DIR}

# xcframeworkの出力先ディレクトリ作成
mkdir -p ${XCFRAMEWORK_DIR}

# iOS実機用のarchiveファイル
ARCHIVE_FILE_IOS=${ARCHIVE_DIR}/ios.xcarchive
echo "ARCHIVE_FILE_IOS:${ARCHIVE_FILE_IOS}"

# iOSシミュレータ用のarchiveファイル
ARCHIVE_FILE_IOS_SIMULATOR=${ARCHIVE_DIR}/iossimulator.xcarchive
echo "ARCHIVE_FILE_IOS_SIMULATOR:${ARCHIVE_FILE_IOS_SIMULATOR}"

# iOS実機用のarchiveファイル作成
xcodebuild archive -scheme ${PROJECT_NAME} -destination="iOS" -archivePath $ARCHIVE_FILE_IOS -derivedDataPath $DERIVED_DIR -sdk iphoneos SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES

# iOSシミュレータ用のarchiveファイル作成
xcodebuild archive -scheme ${PROJECT_NAME} -destination="iOS Simulator" -archivePath $ARCHIVE_FILE_IOS_SIMULATOR -derivedDataPath $DERIVED_DIR -sdk iphonesimulator SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES

# xcframework作成
xcodebuild -create-xcframework -framework $ARCHIVE_FILE_IOS/Products/Library/Frameworks/${PROJECT_NAME}.framework -framework $ARCHIVE_FILE_IOS_SIMULATOR/Products/Library/Frameworks/${PROJECT_NAME}.framework -output $XCFRAMEWORK_DIR/${PROJECT_NAME}.xcframework

# xcframeworkの出力ディレクトリをFinderで開く
open ${XCFRAMEWORK_DIR}

```

ビルドターゲットをAggregateに変更します。そしてProductメニュから→Buildを選択して
ビルドします。
<img width="438" alt="xcframework_p8.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36602/02094fab-b70c-e0cc-de23-04c03032252d.png">
ビルドが成功したら生成されたxcframeworkをFinderやtreeコマンドなどで確認します。
実機用フレームワーク(ios-armv7_arm64以下)とシミュレータ用フレームワーク(ios-i386_x86_64-simulator以下)が両方とも含まれている状態になっていればここまでの手順は完了です。

```treeコマンドの実行例
%tree OreXCFramework.xcframework
OreXCFramework.xcframework
├── Info.plist
├── ios-armv7_arm64
│   └── OreXCFramework.framework
│       ├── Headers
│       │   ├── OreXCFramework-Swift.h
│       │   └── OreXCFramework.h
│       ├── Info.plist
│       ├── Modules
│       │   ├── OreXCFramework.swiftmodule
│       │   │   ├── arm.swiftdoc
│       │   │   ├── arm.swiftinterface
│       │   │   ├── arm64-apple-ios.swiftdoc
│       │   │   ├── arm64-apple-ios.swiftinterface
│       │   │   ├── arm64.swiftdoc
│       │   │   ├── arm64.swiftinterface
│       │   │   ├── armv7-apple-ios.swiftdoc
│       │   │   ├── armv7-apple-ios.swiftinterface
│       │   │   ├── armv7.swiftdoc
│       │   │   └── armv7.swiftinterface
│       │   └── module.modulemap
│       ├── OreXCFramework
│       └── _CodeSignature
│           └── CodeResources
└── ios-i386_x86_64-simulator
    └── OreXCFramework.framework
        ├── Headers
        │   ├── OreXCFramework-Swift.h
        │   └── OreXCFramework.h
        ├── Info.plist
        ├── Modules
        │   ├── OreXCFramework.swiftmodule
        │   │   ├── i386-apple-ios-simulator.swiftdoc
        │   │   ├── i386-apple-ios-simulator.swiftinterface
        │   │   ├── i386.swiftdoc
        │   │   ├── i386.swiftinterface
        │   │   ├── x86_64-apple-ios-simulator.swiftdoc
        │   │   ├── x86_64-apple-ios-simulator.swiftinterface
        │   │   ├── x86_64.swiftdoc
        │   │   └── x86_64.swiftinterface
        │   └── module.modulemap
        ├── OreXCFramework
        └── _CodeSignature
            └── CodeResources
```

# 参考
執筆にあたり以下を参考にさせていただきました。
[Binary Frameworks in Swift - WWDC 2019 - Videos - Apple Developer]
(https://developer.apple.com/videos/play/wwdc2019/416/)
いけだや技術ノート　[Xcode 11で導入されるxcframeworkのディレクトリ構造](http://ikesyo.hatenablog.com/entry/xcode11-xcframework)

明日は、[xcframeworkを作成する（第2回）](https://qiita.com/tfactory/items/07b587a400509314ce63)です。
引き続き、[GMOアドマーケティング Advent Calendar 2019](https://qiita.com/advent-calendar/2019/gmo-am)をお楽しみください！