🤖

5分でわかる、Kotlin Multiplatform Mobile (KMM)

2021/05/05に公開
  • Kotlinの言語思想として Multiplatform Programming というテーマが掲げられている (2021/05/05時点まだ Alpha だけど)

  • 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のプロジェクト構成は以下のようになっていて、このうち sharedcommonMain にて 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を工夫して組んでいる

チームへの導入

感想

  • 既存のプロダクトに部分的に導入することができる点がかなり良い
  • 今はまだ周辺のエコシステムが拡充していないが、よりライブラリ等が充実していけばますます実装コストは削減されていきそう
  • 否定的なことを言うと、以下のような感じかな
    • タスクを出す際に考えることが増える
      • どこまでをKMMモジュールに切り出すか
      • I/Fはどうするか (Android, iOSエンジニアを巻き込んで決める必要あり)
    • (特にモノレポではない場合) KMMモジュールに切り出したドメインオブジェクトやビジネスロジックの変更容易性が下がる
    • チーム (特にiOSエンジニア) への学習コスト
    • 工夫しなければSuspend functionをiOS側でキャンセルできない
  • ↑のあたりが削減される実装コストに見合うかどうかを導入の判断基準にしていきたい

Discussion