5分でわかる、Kotlin Multiplatform Mobile (KMM)
- Kotlinの言語思想として
Multiplatform Programming
というテーマが掲げられている (2021/05/05時点まだ Alpha だけど)- Kotlin Overviewのセクションの一番上に Multiplatform Programmingのセクション があるくらい
-
Kotlin Multiplatform
の概念は下図の通りで、Server, Web Frontend, Mobile Appまで全て一貫してKotlinで書ける未来を目指している (https://kotlinlang.org/docs/multiplatform.html より)
-
Kotlin Multiplatform Mobile (KMM)
はこの中でもさらに Android-iOS間でのコードの共有 に焦点を当てている -
この辺りの概要は以下のスライドが非常にわかりやすい
-
React NativeやFlutterなど他のマルチプラットフォームライブラリとの大きな違いはUIの共通化をしないところ
-
AndroidだとMaterial Design, iOSだとHuman Interface Guidelinesがあり、それぞれのプラットフォームでUIは最適化する必要があるため、KMMではあくまでビジネスロジックの共有に着目している
-
この特徴ゆえに、フルスクラッチで書き直すというよりは、部分的にKMMに移行していくという導入も可能 (むしろそちらの方がやりやすそうに感じる)
プラットフォーム固有のAPI
-
例えばUUIDを生成する処理は各プラットフォームで行いたい場合、下図のようになる (https://kotlinlang.org/docs/mobile/connect-to-platform-specific-apis.html より)
-
expect
で修飾したメソッドのI/FをiOS, Androidそれぞれでactual
を付与したメソッドで実装している -
より具体的に説明すると、KMMのプロジェクト構成は以下のようになっていて、このうち
shared
のcommonMain
にてexpect
キーワードを付与したclassなどを定義する
- その後、それに対応した実装を、同じ
shared
の中にあるiosMain
androidMain
にてそれぞれactual
というキーワード付きで定義する
-
上で貼った例は SQLDelight というSQLiteのライブラリ
- Android, iOSでSQLiteのDriverをそれぞれDIできるようにする必要があるみたい
-
他にも Ktor というNetworking周りのライブラリも用意されていて、Android, iOSの通信周りの処理はKtorによる実装を共有できる
- こっちは特にプラットフォーム固有の実装必要なし
-
(こんな感じで今後もKotlin/Native向けのライブラリは増えていきそう)
-
iOSのSDKをなぜ
shared/iosMain
というKotlinのディレクトリで扱えるのか -
公式ドキュメント (https://kotlinlang.org/docs/mobile/add-dependencies.html#ios-dependencies) をみてみると
Apple SDK dependencies (such as Foundation or Core Bluetooth) are available as a set of prebuilt libraries in Kotlin Multiplatform Mobile projects. They do not require any additional configuration.
- とのことで、なんの追加実装なしでもFoundationやCore BluetoothといったiOSの標準フレームワークはKotlinで使えるようにしてくれているらしい
- しかし気になるのはその下の一文で
You can also reuse other libraries and frameworks from the iOS ecosystem in your iOS source sets. Kotlin supports interoperability with Objective-C dependencies and Swift dependencies if their APIs are exported to Objective-C with the @objc attribute. Pure Swift dependencies are not yet supported.
-
とあり、pureなSwiftで実装されたライブラリはKotlinでサポートできないらしい (not yetと書いてるので将来的には解決されるかも?)
-
shared
に記述した処理はandroidApp
,iosApp
にてimportすることで呼び出すことが可能- ここでAndroid, iOSそれぞれの言語, UIフレームワークを駆使していつも通り実装していける (下図はSwiftUIでiOSのViewが実装されている)
並行処理
- 公式ドキュメント: https://kotlinlang.org/docs/mobile/concurrency-overview.html
- JavaやObjc/Swiftではマルチスレッドでミュータブルなstateに対して同時に変更を加えることが可能となっている
- これは不具合を生みやすい
- Kotlin/Nativeでは以下の2つの制約を課すことでこの問題に対応しようとしている
- Rule 1: ミュータブルなstateへの変更は必ず1つのThreadから実行する
- Rule 2: イミュータブルなstateは複数のThreadから参照可能
- なお、ここでいうイミュータブルはvalによる定数宣言ではなく、
frozen state
を指している - Kotlin/Nativeでは全てのクラスにextensionで
freeze()
メソッドを拡張しており、このメソッドを明示的に呼び出すことでfrozen state
にすることが可能 - もし
frozen state
に変更を加えようとすれば、ランタイムにexceptionを吐く - objectやcompanion objectは暗黙的にfreezeされるため、
frozen state
に気づかず変更を加えてしまいクラッシュさせてしまうこともありそう - この辺りの話は以下のスライドが詳しくてわかりやすい
- 将来的にはfreezeによるこういった工夫も必要ないようにしたいらしい: https://blog.jetbrains.com/kotlin/2020/07/kotlin-native-memory-management-roadmap/
- iOSのAuto Reference Countingのせいでこういう仕様になったっぽい...
- 確かに、Kotlin Naitiveに弱参照のルールとか持ってこれないよなぁ...
- Swift 6に導入されるActorモデルによる改善を期待
CI/CD
- 公式サンプルではモノレポで管理されていて、KMMモジュールもAndroid, iOSのプロジェクトも全て一つのRepositoryに置いてある
- Kotlin Confもモノレポ
- 最初からKotlin Multiplatform前提で設計していくのであればモノレポが楽そうだが、既存プロダクトへの導入を考えると少し難しい
- チームラボさんでは複数のRepositoryに分けてCI/CDを工夫して組んでいる
- https://speakerdeck.com/gmvalentino8/cd (スライドAmong usめっちゃ可愛い)
チームへの導入
- 既存プロダクトへの導入方針や運用方針はかなり手厚くドキュメント化されている
- ABEMAでの導入事例が非常に実践的で参考になる
- どの処理をKMMモジュールに切り出すか、iOSエンジニアがどのようにフローに絡んでくるかを決定していく部分が重要
感想
- 既存のプロダクトに部分的に導入することができる点がかなり良い
- 今はまだ周辺のエコシステムが拡充していないが、よりライブラリ等が充実していけばますます実装コストは削減されていきそう
- 否定的なことを言うと、以下のような感じかな
- タスクを出す際に考えることが増える
- どこまでをKMMモジュールに切り出すか
- I/Fはどうするか (Android, iOSエンジニアを巻き込んで決める必要あり)
- (特にモノレポではない場合) KMMモジュールに切り出したドメインオブジェクトやビジネスロジックの変更容易性が下がる
- チーム (特にiOSエンジニア) への学習コスト
- 工夫しなければSuspend functionをiOS側でキャンセルできない
- タスクを出す際に考えることが増える
- ↑のあたりが削減される実装コストに見合うかどうかを導入の判断基準にしていきたい
Discussion