【KMM】sharedモジュールをXCFrameworkとして生成する

通常のKMMプロジェクトでは、sharedモジュールのコードをiOS側でも使用できるようにFrameworkとして生成している。
ここでは、sharedモジュールをFrameworkではなくXCFrameworkとして生成する方法をまとめる。

# 環境
- KMM：v0.3.3
- Kotlin：v1.7.0
- iOS framework distribution：Regular framework
- NewProjectでKotlin Multiplatform Appを選択した状態とする

# デフォルトで生成されるFrameworkについて
NewProjectでKotlin Multiplatform Appを選択してビルドした場合、初期状態ではsharedモジュールをFrameworkとして生成するようになっている。
Frameworkは RootProject/shared/build/bin/に環境ごとに生成される。
- Arm64用のFramework
- Simulator Arm64用のFramework
- X64用のFramework
![スクリーンショット 2022-08-27 16.20.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/ce88a9d6-72c1-c609-3a13-d674d3663552.png)
Frameworkの場合だとArm64の実機用、Arm64のシミュレータ用など異なったフレームワークを使用することになるが、XCFrameworkを使用することで、統一して1つのフレームワークを使用することが可能になる。

# XCFrameworkとして生成する方法

1. **build.gradle.ktsで以下を追加する**

    ```gradle
    // 追加1
    import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFramework
    ...
    
    kotlin {
    	// 追加2
    	val xcf = XCFramework()
        listOf(
            iosX64(),
            iosArm64(),
            iosSimulatorArm64()
        ).forEach {
            it.binaries.framework {
                baseName = "shared"
                // 追加3
                xcf.add(this)
            }
        }
    }
    ```

2. **gradlewコマンドでビルドする**
    ```bash
    $ ./gradlew build

    ...
    BUILD SUCCESSFUL in 1s
    ```
    
    するとXCFrameworkを生成する以下のタスクが登録される。
    
    - `assembleXCFramework`
    - `assembleSharedDebugXCFramework`
    - `assembleSharedReleaseXCFramework`

    実際にタスクが登録されているかは以下のコマンドで確認できる。（DebugとReleaseのコマンドに関しては表示されない）
    ```bash
    $ ./gradlew tasks

    ...
    assembleXCFramework - Assemble all types of registered 'shared' XCFramework
    ```
    
    assembleXCFrameworkはDebug用とRelease用の両方を生成するため、状況に合わせてDebugのXCFrameworkのみを生成するassembleSharedDebugXCFrameworkを使用するなどすると良さそう。

3. **生成コマンドを叩く**

    ```bash
    $ ./gradlew assembleXCFramework
    ```

これでXCFrameworkが生成された

## 生成されたXCFrameworkについて
XCFrameworkは RootProject/shared/build/XCFrameworks/に生成される。
![スクリーンショット 2022-08-27 16.49.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/25cad713-788e-ef5c-65ec-3b50de87fd55.png)
前述した通り、XCFrameworkの場合Frameworkの時のようにArm64用・X64用のような環境ごとに生成はされず、1つのXCFrameworkにまとめられている。

# Xcode側でXCFrameworkを参照する

1. **Run Scriptの変更**
    Build SettingのKMM用のRun Scriptに先ほど生成した`assembleXCFramework`コマンドを設定することで、Xcodeでビルド時にXCFrameworkを生成させる
    ```bash
    # デフォルトが以下
    cd "$SRCROOT/.."
    ./gradlew :shared:embedAndSignAppleFrameworkForXcode
    
    # 変更後
    cd "$SRCROOT/.."
    ./gradlew :shared:assembleSharedDebugXCFramework
    ```

2. **Framework Search Pathsの変更**
    Build SettingのFramework Search Pathsを、XCFrameworkのpathへと変更する
    
    ```bash
    # デフォルトが以下
    $(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)
    
    # 変更後
    $(SRCROOT)/../shared/build/XCFrameworks
    ```

3. **Other Linker Flagsの変更**
    Buid SettingのOther Linker Flagsに設定されているの以下が不要になるので削除する

    ```bash
    # 削除する
    $(inherited) -framework shared
    ```

4. **Frameworks, Libraries, and Embedded Contentへの追加**
    shared.xcframeworkモジュールを追加する。
    +ボタン → Add Other Filesから、生成された.xcframeworkを選択する
    ![スクリーンショット 2022-08-27 17.27.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/2bcffccb-3dba-d66f-f51e-63d4bb69f340.png)
    ↓
    ![スクリーンショット 2022-08-27 17.27.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/b8a56a23-6433-3871-28b7-6973a5be6ec3.png)


これでXCFrameworkを参照できるようになった。
.swiftファイルでimport sharedするとエラーが出ないことを確認できる。
```swift
import shared
```


# XCFrameworkの名前を指定している場合

build.gradle.ktsで XCFrameworkの名前を指定した場合、コマンドは以下のように変更となる

- `assemble<”XCFrameworkの名前”>XCFramework`
- `assemble<”XCFrameworkの名前”>DebugXCFramework`
- `assemble<”XCFrameworkの名前”>ReleaseXCFramework`

<”XCFrameworkの名前”>の箇所は、build.gradle.ktsで XCFramework(””)を生成したときに指定した名前を入れる。

以下の例だと”Hoge”が入っているので

```bash
val xcf = XCFramework("Hoge")
```
`assembleHogeXCFramework`となる。

XCFrameworkの名前が変わっているので、importするときもsharedではなく、import Hogeになる

```swift
import Hoge
```
# 終わりに
最近KMMを触る機会が多いのですが、iOSエンジニアがKMMを触るのはGradleだったりAndroid周りの知識だったりが必要なので学習コストが高いなと感じました。
KMM触る時は是非身近なAndroidエンジニアを巻き込むことをお勧めします。

## 参考
- [Understand mobile project structure](https://kotlinlang.org/docs/multiplatform-mobile-understand-project-structure.html)
- [Make your Android application work on iOS – tutorial](https://kotlinlang.org/docs/multiplatform-mobile-integrate-in-existing-app.html)
- [Build final native binaries](https://kotlinlang.org/docs/multiplatform-build-native-binaries.html)
- [multiplatformmobile samples](https://kotlinlang.org/docs/multiplatform-mobile-samples.html)