KMMによるAndroid/iOS向けライブラリ開発の所感
この記事は、NAVITIME JAPAN Advent Calendar 2021 の18日目の記事です。
みなさんこんにちは、三代目ゆうです。ナビタイムジャパンでAndroid / iOSアプリ向け地図フレームワークやAR描画フレームワークの開発を担当しています。
Android / iOS向けのAR描画フレームワークを開発するにあたりKotlin Multiplatform Mobileこと KMM を活用したため、今回はそのメリット・デメリット含めた所感についてお話したいと思います。
※あくまでKMMを活用してネイティブアプリ向けのライブラリ開発をした話であり、KMM向けライブラリとは異なります。ご承知おきください。
はじめに
突然AR描画フレームワークという単語が出てきましたが、当社ではAR技術を活用したナビゲーションに取り組む動きが活発になっております。その代表例としてAI / ARを活用した運転支援ドラレコアプリの「AiRCAM」を3月にリリースしました。
しかし、こういったAR技術というものはやはり実装コストが高く、各サービスごとにアプリ開発者が実装しようと思うと工数が跳ね上がってしまいます。そのため、手軽に取り込み利用できる社内向けのフレームワークとして開発し、先述の「AiRCAM」を含む複数のサービスで利用してもらう形となりました。
さて、そこで要件として上がるのが「Android / iOSの両OS向けにそれぞれネイティブ環境で使えるライブラリ」を用意することでした。もちろん、この場合ライブラリ自身もそれぞれのネイティブ環境で開発するのが一番シンプルではあるのですが、開発スケジュールの都合や今後の運用によるOS間仕様差分が発生する懸念など、開発・運用コストの削減を狙いソースコードを共通化したいと考えKMMの導入に至りました。
なぜKMMか?
アプリ界隈でクロスプラットフォーム開発という話になると、よくFlutterやReactNativeとの比較が上がるかと思いますが、今回は主に下記2点の理由からKMMを選定しました。
ネイティブアプリから使いやすい
繰り返しになりますが、今回の開発物は「Android / iOSのネイティブアプリから利用されるフレームワーク」であり、各ネイティブ環境から使いやすくなっている必要がありました。
そのため、ネイティブコードと共通コード間での相互運用性が高い事が必須です。
KMMではiOSはKotlin/NativeでビルドされることによりネイティブコードからはObj-Cフレームワークと同じように見えるため自然に扱えますし、Androidに至ってはネイティブコードと殆ど変わらない扱い方が可能です。もちろん、FlutterやReactNativeにもネイティブコードと相互呼び出しを可能にする仕組みはありますが、そのスマートさにおいてはKMMが一つ抜きん出ている印象です。
Androidの資産が活かせる
こちらは開発経緯による都合のところが大きいのですが、今回のAR描画フレームワークは当初Android専用として開発をスタートしており、途中からiOS版も要件に上がってくるという流れがありました。
そのためAndroid向けとしてKotlinですでにほぼ出来上がっているコードのうち、共通化したいクラス郡だけ切り出しKMMの共通モジュールとして落とし込むことで大幅にiOS版の開発コストを減らせる見込みが立っていました。
結果として、かなり短い期間でiOS版フレームワークのリリースに漕ぎ着けることができました。
全体の構成
KMMを活用してAndroid / iOSの両OS向けフレームワークを作るに当たり、全体のモジュール構成は下図のような形に落ち着きました。
実際にKMMによって共通化するモジュールは共通層として、CommonKotlin(ピュアなKotlin)で記述されます。主なロジックや各種データ構造などを持ち、またプラットフォーム層へのアクセスを行うI/Fを持っています。共通層は直接アプリに公開せず、各プラットフォームネイティブなモジュールによって隠蔽する形にしています。
プラットフォーム層は、各プラットフォームのViewシステムとの接続や各種入力の受付などネイティブ実装が必要な箇所と、共通層が提供するI/Fの実装、さらに外部にAPIを提供する役目を持っています。
common
CommonKotlinで記述される共通モジュール層
共通のロジックやデータクラスなどを持つ
view-android
Kotlin/JVMで記述されるAndroid用プラットフォーム層
view-ios
Swiftで記述されるiOS用プラットフォーム層
KMMを導入してみた結果
コード共通化を目的としている以上、共通化率がどの程度になったかは気になるところだと思います。今回、コード行数換算で 約62% のコードが共通化できていました(前述の構成図におけるview-androidとcommonの合計数のうちcommonの占める割合)。
これを多いと見るか少ないと見るかは人によって意見が分かれるところかと思いますが、プラットフォーム層側には外部提供APIのための土管クラスなども含まれるため、自分の体感としてはかなり多くの部分を共通化できたのではないかなと思いました。
続いて、実際に開発していて感じたメリット・デメリットについて簡単に共有したいと思います。
メリット
Androidでは、ほぼネイティブ開発と変わらない感覚で実装できる
当たり前ですが同じKotlinなので、プラットフォーム層も共通層もほぼほぼそのままの感覚で実装できます
デメリット側にも書きますが、Kotlin/JVMの感覚に慣れてると最初は少し戸惑うところはありました
ネイティブコードとの相互運用性が高く柔軟なAPI設計が可能
クロスプラットフォーム開発経験者ならあるあるだと思うのですが、プラットフォーム層と共通層の間で受け渡しできるデータに制限があり、黒魔術的実装により回避する…といった工夫が不要でした
Kotlinで書ける
今、実装していて最も気持ちのいい言語 [*要出典] であるKotlinで書けるだけで最大のメリットと言えますね
デメリット
Javaの資産が使えないのが意外と困る
普段Kotlin/JVMで開発していると、意外とjavaパッケージのクラスに頼ってることが多かった事がわかりました
慣れてしまえばそこまで苦労する事はなく、ピュアなKotlinとJavaに近いKotlinとの差に対して勘所が付いていいかもしれません
iOS側でのデバッグが難しい
iOS側で動かす際にはKotlinコードは実行可能バイナリの状態になっているため、デバッガで処理を追うといった事ができません
基本的なロジックのデバッグはAndroid側でできるので問題ないのですが、稀にiOSでだけ再現するような事象が起きると、多少しんどい思いをしました
Kotlin/Nativeのパフォーマンスがやや悪い
Android側では問題にならなかった愚直な実装のロジックが、iOS側でだけ許容できないパフォーマンス悪化に繋がることが何度かありました
Kotlin/Nativeのパフォーマンス問題は公式に重点的な改善箇所として挙げられているので今後に期待ですね
おわりに
KMMはまだα版ということもありプロダクトで導入するにはやや挑戦的な試みになるかとは思いますが、それでも現時点で十分すぎるほどにメリットが感じられました。今後の発展が非常に楽しみな技術の一つです。
最後に、クロスプラットフォーム開発技術の選定に迷っている人にとってこの記事が一助となりましたら幸いです。