iOSアプリからKMPで定義したsuspend関数を呼ぶ

これは Kyash Advent Calendar 2024 の22日目の記事です。KyashでiOSアプリを開発している tamadon です。

Kyashのモバイルアプリ開発では KMP(Kotlin Multiplatform) を採用しています。「KMPとはなんぞや?」「KyashでKMPの構成は?」については以下の記事を御覧ください。

AndroidとiOSのエンジニアでKMPの実装をペアプロしている話

この記事で掲載している構成図を紹介します。新規でKMPを使用する画面はこの構成で実装しています。

https://cdn-ak.f.st-hatena.com/images/fotolife/r/rmakiyama/20231205/20231205094930.png

KMPの採用を積極的に進めていった結果、既存の画面に機能を追加する際にReactorを作成して画面全体を作り直す程ではないけど、UseCase層まではKMPで実装し処理を共通化したいという要望が上がってきました。

今回はサンプルなのでこういった仮のsuspend関数があると仮定します。

class ExampleUseCase {
    suspend fun exampleFunction() {
        // Do something
    }
}

KMPを採用していない既存の画面は以下のような構成になっています。このViewModelにKMPで実装したUseCaseを追加するイメージです。

こちらをSKIE(スカイ)の機能を利用して実現する方法を紹介します。 SKIEの説明については以下の記事を参照ください。

Kotlin Multiplatform の SKIE (スカイ)を導入して、iOS エンジニアの開発者体験を改善しました。

SKIE公式ドキュメントの Suspend Fuctions の記述をまとめると、SKIEを使用するとこういうメリットがあるようです。

  • Kotlinのsuspend関数を呼ぶときにキャンセルが可能になる
  • メインスレッド以外でもsuspend関数を呼べるようになる

本記事ではKyashで実際に使用した機能を紹介したいので、Kotlinのsuspend関数を呼ぶときにキャンセルが可能になる機能は紹介しません。

参考記事にこちらの機能についても紹介されている 当社メンバーが書いた記事へのリンクを記載します。ご興味があれば合わせて読んでみてください。

まず既存のViewModelにKMPのUseCaseを追加し、それを呼び出すメソッドを追加します。

    func callExampleUseCase() async {
        let usecase = ExampleUseCase()

        try? await usecase.exampleFunction()
    }

上記メソッドをViewControllerからこのように呼び出します

  Task {
      await self.viewModel.callExampleUseCase()
  }

ViewModelから実行される処理がmainスレッドであれば特に問題になりませんが、こちらがmainスレッド以外の時に問題が発生します。

ということで、試しにSKIEの機能を使用せずにmainスレッド以外からKotlinのsuspend関数を呼び出してみます。

  Task.detached {
      await self.viewModel.callExampleUseCase()
  }

下記エラーが発生しクラッシュしました 💥

Calling Kotlin suspend functions from Swift/Objective-C is currently supported only on main thread

こんな時はどうすれば良いでしょうか。SKIEの出番となります。

下記記事にあるように、Kyashでは余分なビルド時間を作らないようにSKIEのSuspend Functions機能は無効化されています。

Kotlin Multiplatform の SKIE (スカイ)を導入して、iOS エンジニアの開発者体験を改善しました。

そのため、以下のように SuspendInterop.Enabled アノテーションを付けることで使いたい箇所だけ有効化しています。

class ExampleClass {
    @SuspendInterop.Enabled
    suspend fun exampleFunction() {
        // Do something
    }
}

前述のmainスレッド以外からKotlinのsuspend関数を呼んでみます。無事メインスレッド以外でのsuspend関数の呼び出しに成功しました 👏

なぜSKIEを導入するとうまくいくのか、詳しくは下記公式の記事を参照いただければと思いますがKotlinとSwiftのスレッド生成時の挙動差異をSKIEが吸収してくれるからだそうです。すごい。

Suspend Functions - Migration and Compatibility | SKIE

このようにKyashでは歴史のある画面でも新しい処理を追加する際には、なるべくKMPを使用してロジックを共通化することで開発効率の向上と認知負荷の軽減に取り組んでいます。

この記事を見た皆さんのプロダクトでも「画面を作り変えるのはコストが高いけど、一部のロジックだけでも共通化できるんだったら便利かも」と思っていただけたら幸いです。

最後に

Kyashではエンジニアを募集しています。少しでもご興味があれば下記リンクからどうぞ!

  • さっそく応募したいぞという方はこちらから!
  • まずはカジュアルに話を聞いてみたいぞという方はこちらから!!

お会いできるのを楽しみにしています。

参考記事