Swift Exportはどこから来たのか、Swift Exportは何者か、Swift Exportはどこへ行くのか

こんにちは。Androidエンジニアの豊川です。

この記事は、[kubell Advent Calendar 2024](https://qiita.com/advent-calendar/2024/kubell)（シリーズ 1）の13日目の記事です。

---

先日、Kotlin Multiplatform (KMP)のロードマップが更新されました。

魅力的な内容がたくさんありましたが、自分は特に[Swift Exportがロードマップに加わること](https://blog.jetbrains.com/kotlin/2024/10/kotlin-multiplatform-development-roadmap-for-2025/#kotlin-to-swift-export)へ注目しています。

この機能は以前から要望が多く、気になっていた方も多いのではないでしょうか。

加えてKotlin 2.1.0にて[早期アクセス](https://kotlinlang.org/docs/whatsnew21.html#basic-support-for-swift-export)が始まり、今後のアップデートがますます楽しみです。

一方で、Swift Exportは公開されて間もないため、情報が分散しており、調べる中で理解するのに手間がかかると感じました。
そこで本記事では、Swift Exportについて調査し、どこから来たのか(なぜ作られたか)、何者か(何ができるか)、どこへ行くのか(どうなるのか)、の観点でまとめていきたいと思います。

:::note alert
この先に記載する内容には初期段階の情報が含まれています。
 
今後変更されたり、そもそも廃止される可能性がありますので予めご承知おきください。

また、筆者もKMPの知識が万全ではないため、誤った情報が含まれている可能性があります。その際はご指摘いただけると幸いです。
:::

## 要約
- Swift Exportはまだアルファリリースにも至っていない
  - 場合によっては[**機能自体が廃止される可能性も**](#機能の廃止に関して言及している箇所)ある
- Objective-Cを介して実行していたものがSwiftを介して実行するようになる
  - KMPのiOS開発が快適になる
- 実装を理解するにはKotlinを読む必要がある(全てをSwiftに変換するわけではない)
- 現時点(Kotlin 2.1.0)でできることは限られている
  - `final class`のExport
  - マルチモジュールのExport
  - Gradle Moduleに対してカスタムのSwift モジュール名を設定する
  - flattenPackage プロパティを使用し、パッケージ構造を簡略化するルールの設定
- First Releaseで既存のObjective-C Exportと同等の機能を提供する予定
- 順調にいけば Enum, data class/object, Sealed class/interface, Flowなどがサポートされる可能性もある


## Swift Exportはどこから来たのか(なぜ作られたか)
Swift Exportの話が登場したのは、私の観測範囲では[2024年のロードマップでの言及](https://blog.jetbrains.com/kotlin/2023/11/kotlin-multiplatform-development-roadmap-for-2024/#multiplatform-core)が初めてだったと記憶しています。

どのような動機で作られたかに関しては、Swift Export のアーキテクチャに関するドキュメントの[A bit of history: Objective-C export](https://github.com/JetBrains/kotlin/blob/master/docs/swift-export/architecture.md#a-bit-of-history-objective-c-export)で言及されています。

従来の Objective-C Exportでは
- Kotlin宣言記述子のツリーからObjective-C宣言を生成する
- これらのObjective-C宣言を対応するバックエンドIRノードに関連付ける
- Objective-C <-> Kotlin ブリッジの LLVM IR(LLVMコンパイラにおける中間表現) を生成する
- すべてをAppleのフレームワークにまとめる(.framework)

というアプローチで実装されていました。これらはうまく動作していたのですが、下記のような課題がありました。
-	IDEとの統合が難しい
-	Kotlin/NativeにおけるLLVM IRジェネレーターが2つ存在する
-	K1のレガシーであるディスクリプターに依存している
-	フレームワーク以外の成果物との統合が困難

例えば、『IDEとの統合が難しい』『K1のレガシーであるディスクリプターに依存している』という点は、普段Kotlin、特にKMPを利用している開発者なら直感的に理解しやすいのではないでしょうか。

これらの課題を解決する、という目的に加え、Analysis APIの登場、klib（Kotlin/Nativeで使用される、Kotlinコードとメタデータを内包するライブラリ形式）, LLVM IRの周辺の成熟という背景も相まって、Swift Exportの開発につながりました。

## Swift Exportは何者か
### 何者か
端的に言うと、Kotlin で書いたコードを、Objective-Cを介さずにSwiftから利用できるようにする機能です。

従来のKMPでは概ね下記のような流れでKotlinのコードをSwiftから利用していました。

```
Kotlin -> Kotlin/Native -> ネイティブバイナリ -> **Objective-C(.h)** <- Swift 
```

Kotlinで実装されたコードをネイティブバイナリにし、そのインターフェースとしてObjective-Cのヘッダーファイルを介してSwiftからアクセスする、というイメージです(これをドキュメント内ではObjective-C Exportと呼んでいます)

Swift Exportを使うことで、下記のように変わります

```
Kotlin -> Kotlin/Native -> ネイティブバイナリ -> **Swift(.swift)** <- Swift
```

インターフェースであるObjective-C(.h) が Swift(.swift) に変わるため、Objective-Cを介することなくKotlinのコードをSwiftから利用することができるようになり、KMPにおけるiOS開発のストレスを軽減することが期待できます。

### 何者ではないか
**SwiftをKotlinにExportする機能ではない**

[こちらのスレッド](https://slack-chats.kotlinlang.org/t/18895264/hello-at-first-i-would-like-to-thank-all-of-the-swift-export#87143b0a-a7f2-4142-94c2-6b7d6e689e89)で言及されているように、SwiftをKotlinにImportする機能ではありません(名前的に紛らわしいのですが)
 
上記の機能は便宜上Swift Importと呼ばれており、Swift Exportよりも大きく、難しいプロジェクトのため、Swift Exportよりも先にリリースされることはない、とも言われています。

そのため厳密には“Kotlin to Swift Export”という名称のほうがわかりやすいかもしれません。(実際、2024年のロードマップでは"direct Kotlin-to-Swift export"と呼ばれています)

**詳細な実装をSwiftから読み解けるわけではない**

Swift Exportという名前から、Kotlinで書かれたコードをSwiftに変換する機能のように思えるかもしれません(自分も最初その印象を抱いていました)がこれは誤りです。

先述でも少し触れていますが、Kotlinで実装したコードの詳細はネイティブバイナリに内包されます。そのため実装を理解するにはネイティブバイナリかKotlinを読む必要があります。
そのため基本的にはKotlinを読むことが必要になるでしょう。

### Swift Export は何ができるか
**現時点(Kotlin 2.1.0)でできること**

[Basic support for Swift export](https://kotlinlang.org/docs/whatsnew21.html#basic-support-for-swift-export) を見てわかるように現在 Swift Exportでできることは下記の4つです
- `final class`(継承することができないクラス)のExport
- マルチモジュールのExport
- Gradle Moduleに対してカスタムのSwift モジュール名をつけることができる
- flattenPackage プロパティを使用し、パッケージ構造を簡略化するルールの設定

これ以外、例えば `open/abstract class`などの基本的な要素もまだサポートされていません。
まだアルファ版にも達していないので当然ですが、できることは限られています。

**First Releaseでできること**

上記に加えて [First Release](https://youtrack.jetbrains.com/issue/KT-64572#august-2024)では下記の機能がサポートされる予定です 
- interfaceのExport
- open/abstract classeのExport(これに関しては現在進行中のようです。)
- Swift ObjectをKotlinの関数に渡すことができる
- Swiftのclass, structに Kotlinのinterface,class継承する

### Swift Exportは何ができないか
First Releaseでは下記はサポートされない予定です
- Enum 
- Sealed interface/class 
- data class/object
- ライブラリ型のカスタム変換(kotlinx.coroutines.Flow を AsyncSequence に変換)

First ReleaseではObjective-C Exportと同等の機能をサポートすることを目指しているため、意図的に優先度を落としていることが[こちらのIssue](https://youtrack.jetbrains.com/issue/KT-64572/The-first-public-release-of-Swift-Export#summary-of-the-support-for-kotlin-classes-and-interfaces)から読み取れます。

反面、順調に進んでいけばこれらの機能もサポートされていくとも捉えられるので、期待してもよいでしょう。

:::note info
上記の機能(Sealed interface, Flowなど)はKMPでもうまくサポートされておらず、SKIEなどサードパーティのライブラリを使うことで対応している場合があります。

Swift Exportが上記を対応すれば、これらのライブラリを使わずに済む可能性があります。
:::

### Swift Exportを使うことでコードはどう変わるか
目を引く変化で言うと、下記のように変わります。
- ヘッダーファイル(.h)からSwiftファイル(.swift)になる
- モジュール毎にファイルが定義される
- トップレベル関数が使いやすくなる
- `typealias`が使えるようになる

次の項目から実際のコードを交え、Swift Exportを使うことでどのようにコードが変わるのかを見ていきましょう。

#### 参考コード
下記の公式のサンプルコードを元に説明していきます。

https://github.com/Kotlin/swift-export-sample

#### ヘッダーファイル(.h)からSwiftファイル(.swift)になる
先述している通り、ヘッダーファイルがSwiftファイルに変わります。
例えばサンプルの[MyClass](https://github.com/Kotlin/swift-export-sample/blob/master/shared/src/commonMain/kotlin/com/github/jetbrains/swiftexport/Common.kt)で比較して見ましょう

**MyClass**
```kotlin
class MyClass(val property: Int) {

    class Nested(val nestedProperty: Int)
}

typealias MyNested = MyClass.Nested

fun sum(a: MyClass, b: MyNested): Int =
    a.property + b.nestedProperty

fun sharedFunction(): Int = 15
```

**ヘッダーファイル(.h)の場合**
```objectivec
__attribute__((swift_name("MyClass")))
@interface SharedMyClass : SharedBase
- (instancetype)initWithProperty:(int32_t)property __attribute__((swift_name("init(property:)"))) __attribute__((objc_designated_initializer));
@property (readonly) int32_t property __attribute__((swift_name("property")));
@end

__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("CommonKt")))
@interface SharedCommonKt : SharedBase
+ (int32_t)sharedFunction __attribute__((swift_name("sharedFunction()")));
+ (int32_t)sumA:(SharedMyClass *)a b:(SharedMyClassNested *)b __attribute__((swift_name("sum(a:b:)")));
@end
```

**Swiftファイル(.swift)の場合**
```swift
public final class MyClass : KotlinRuntime.KotlinBase {
    public var property: Swift.Int32 {
        get {
            return com_github_jetbrains_swiftexport_MyClass_property_get(self.__externalRCRef())
        }
    }
    public init(
        property: Swift.Int32
    ) {
        let __kt = com_github_jetbrains_swiftexport_MyClass_init_allocate()
        super.init(__externalRCRef: __kt)
        com_github_jetbrains_swiftexport_MyClass_init_initialize__TypesOfArguments__Swift_UInt_Swift_Int32__(__kt, property)
    }
}

public static func sharedFunction() -> Swift.Int32 {
    return com_github_jetbrains_swiftexport_sharedFunction()
}

public static func sum(
    a: MyClass,
    b: MyNested
) -> Swift.Int32 {
    return com_github_jetbrains_swiftexport_sum__TypesOfArguments__ExportedKotlinPackages_com_github_jetbrains_swiftexport_MyClass_ExportedKotlinPackages_com_github_jetbrains_swiftexport_MyClass_Nested__(a.__externalRCRef(), b.__externalRCRef())
}
```

まだpackageなどの冗長な記述はあるものの、大分人間が読める形になったのではないでしょうか。


#### モジュール毎にファイルが定義される
Objective-C Exportの場合、下記のように一つのヘッダーファイルに定義されていました。

```objectivec
__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("Module_aClassFromA")))
@interface SharedModule_aClassFromA : SharedBase
- (instancetype)initWithName:(NSString *)name __attribute__((swift_name("init(name:)"))) __attribute__((objc_designated_initializer));
- (NSString *)hello __attribute__((swift_name("hello()")));
@end

__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("IOSSourceKt")))
@interface SharedIOSSourceKt : SharedBase
+ (int32_t)iosModuleABar __attribute__((swift_name("iosModuleABar()")));
@end

....


__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("Module_bClassFromB")))
@interface SharedModule_bClassFromB : SharedBase
- (instancetype)initWithName:(NSString *)name __attribute__((swift_name("init(name:)"))) __attribute__((objc_designated_initializer));
- (NSString *)hello __attribute__((swift_name("hello()")));
@end

__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("IOSSourceKt")))
@interface SharedIOSSourceKt : SharedBase
+ (int32_t)iosModuleBBar __attribute__((swift_name("iosModuleBBar()")));
@end
```

Swift Exportを使うことで次のようにモジュール毎にファイルが定義されます。

モジュールA
```swift
import ExportedKotlinPackages
import KotlinRuntime

public typealias ClassFromA = ExportedKotlinPackages.com.github.jetbrains.modulea.ClassFromA

public func iosModuleABar() -> Int32

extension com.github.jetbrains.modulea {

    final public class ClassFromA : KotlinBase {

        public init(name: String)

        public func hello() -> String
    }

    public static func iosModuleABar() -> Int32
}
```

モジュールB
```swift
import ExportedKotlinPackages
import KotlinRuntime

public typealias ClassFromB = ExportedKotlinPackages.com.github.jetbrains.moduleb.ClassFromB

public func iosModuleBBar() -> Int32

extension com.github.jetbrains.moduleb {

    final public class ClassFromB : KotlinBase {

        public init(name: String)

        public func hello() -> String
    }

    public static func iosModuleBBar() -> Int32
}
```

#### トップレベル関数が使いやすくなる
今まで定義したトップレベル関数は`FileNameKt.functionName()`という形で使う必要がありました。 
例えば下記のような記述の仕方です。
```swift
let moduleA = CommonKt.useClassFromA()
Text("Module A: \(moduleA.hello())")
let moduleB = CommonKt.useClassFromB()
Text("Module B: \(moduleB.hello())")
```

Swift Exportを使うこと下記のようにでKotlin, Swiftと同じように`functionName()`という形で使うことができます。

```swift
let moduleA = useClassFromA()
Text("Module A: \(moduleA.hello())")
let moduleB = useClassFromB()
Text("Module B: \(moduleB.hello())")
```


#### Typealiasが使えるようになる
今まで Objective-C Exportでは Kotlinで定義したTypealiasをSwiftで使うことはできませんでした。 
そのため、今回のサンプルのコードの`MyClass.Nested`のインスタンスを生成する必要がある場合は愚直に

```swift
let nestedClass = MyClass.Nested(nestedProperty: 6)
```
と書く必要がありました。

Swift Exportを使った場合、下記のように記述することができます。

```swift
let nestedClass = MyNested(nestedProperty: 6)
```

## Swift Exportはどこへ行くのか
前項でも触れてはいますが、[Swift Exportのissue](https://youtrack.jetbrains.com/issue/KT-64572?_gl=1*1auw782*_gcl_au*MTUxNTM3NTc5Mi4xNzI1NDQ5ODg4*_ga*MTIzOTMwNTIyNi4xNzAyNjg5NzQ3*_ga_9J976DJZ68*MTczMzE0NDg5Ni40MS4wLjE3MzMxNDQ4OTYuNjAuMC4w)で言及されているように、Objective-C Export と同等の機能セットがFirst Releaseで提供される予定です。

時期は未定ですが2025年のRoadmap Itemとなっているので、順調にいけば2025年中にリリースされることが予想されます。

First Releaseで無事安定すればそこに加え、現時点ではスコープから外れているEnum, data class, sealed class/interface, Flowなどがサポートされていくはずです。

上記がサポートされていけばKMPにおけるiOS開発がさらに快適になることや、Objective-C Exportと比べて機能の追従もしやすくなることが期待されます。(Swift ExportではK2を利用しているため、パフォーマンスにもいい影響を与えるかもしれません)

## 終わりに
今回はSwift Exportについて調べました。

調べていく中でSwift Export以外にもKotlin/Nativeに関する情報も知ることができ、思わぬ収穫でした。

KMPは毎年進化していて、Androidアプリ開発に身を置くものとしては非常に楽しいですね(キャッチアップも大変ではありますが)

まだアルファより前の段階、かつ[開発中止の可能性も示唆されていますが](#機能の廃止に関して言及している箇所)、個人的には非常に期待しているのでうまくいってほしいなと思っています。

今後のSwift Exportの進捗が気になる方は [Youtrack](https://youtrack.jetbrains.com/issues?q=Subsystems:%20%7BNative.%20Swift%20Export%7D%20)で確認ができますので、興味があればチェックしてみてください。

最後まで読んでいただきありがとうございました！

## 参考資料
### Roadmap
- [Kotlin Multiplatform Development Roadmap for 2024](https://blog.jetbrains.com/kotlin/2023/11/kotlin-multiplatform-development-roadmap-for-2024/)
- [Kotlin Multiplatform Development Roadmap for 2025](https://blog.jetbrains.com/kotlin/2024/10/kotlin-multiplatform-development-roadmap-for-2025/)

### Kotlin
- [What's new in Kotlin 2.1.0](https://kotlinlang.org/docs/whatsnew21.html)

### Kotlin/Native
- [Get started with Kotlin/Native in IntelliJ IDEA](https://kotlinlang.org/docs/native-get-started.html)
- [Kotlin Overview - Kotlin Native](https://kotlinlang.org/docs/native-overview.html)
- [Kotlin/Native README.md](https://github.com/JetBrains/kotlin/blob/master/kotlin-native/README.md)

### Swift Export
- [Kotlin Multiplatform Development Roadmap for 2024#Multiplatform core](https://blog.jetbrains.com/kotlin/2023/11/kotlin-multiplatform-development-roadmap-for-2024/#multiplatform-core)
- [Kotlin Multiplatform Development Roadmap for 2025#Kotlin-to-Swift export](https://blog.jetbrains.com/kotlin/2024/10/kotlin-multiplatform-development-roadmap-for-2025/)
- kotlin/docs
  - [swift-export/README.md](https://github.com/JetBrains/kotlin/blob/master/docs/swift-export/README.md)
  - [swift-export/architecture](https://github.com/JetBrains/kotlin/blob/master/docs/swift-export/architecture.md)
  - [swift-export/compiler-bridges.md](https://github.com/JetBrains/kotlin/blob/master/docs/swift-export/compiler-bridges.md)
  - [swift-export/language-mapping.md](https://github.com/JetBrains/kotlin/blob/master/docs/swift-export/language-mapping.md)
  - [swift-export/standalone_execution_model.md](https://github.com/JetBrains/kotlin/blob/master/docs/swift-export/standalone_execution_model.md)

### 機能の廃止に関して言及している箇所
[What's new in Kotlin 2.1.0](https://kotlinlang.org/docs/whatsnew21.html#basic-support-for-swift-export)

> [!CAUTION]
>
> This feature is currently in the early stages of development. **It may be dropped or changed at any time.** Opt-in is required (see the details below), and you should use it only for evaluation purposes. We would appreciate your feedback on it in YouTrack.