こんにちは、モバイル開発のandrekandore (かんちゃん)だ!
※ この記事のどこかにiOSDC Japan 2023のチャレンジトークンが隠されているよ!
はじめに
この記事は、Kotlin Multiplatformを用いて実現しようとしたこと、運用してみた結果の振り返りについての紹介である。特にSwift・iOSの技術者の観点からどのような課題があったかについて解説する。
iOS / Androd 両プラットフォームをまたがる課題
モバイルアプリ開発においてよく課題としてあがるのは、各プラットフォームにまたがる「ビジネスロジック」の共通化である。
モバイルでは、ウェブとの差異はもちろん、AndroidやiOSアプリとの(仕様・実装の)差異が開発時間の増加につながってしまうことがある。各プラットフォームの差異を埋め合わせることに時間を割くことが、ユーザへの新規機能の実装時間を減らすこととなってしまったり、ハードワークにつながってしまうようなことはないようにしたい。
そういったことを防ぐべく、何か良い仕組みははないかと両プラットフォームにおける解決策を模索しはじめた。
マルチプラットフォームとは何か
マルチプラットフォームとは、複数のプラットフォーム(例えばiOSやAndroid、Webなど)との間で、同じ技術基盤によって、同じロジックをネイティブのUIにて橋渡しする仕組みやフレームワークである。
例えば以下のようなアプローチが存在する
- C、 C++
- Swift (SCADE)
- Kotlin(Multiplatform)
- Rust(SWAPI)
- BFF(API)
- など
それに対して、「クロスプラットフォーム」の開発は、ターゲットプラットフォームのUIまでの全体の隠蔽を目的にし、完全に独立したAPIを提供するもの。
今回話すマルチプラットフォームソリューションはKotlin Multiplatform(KMP)についてである。
Kotlin Multiplatformを採用するに至った背景
ネイティブUIの重視
freeeのモバイルチームでは、最善なユーザー体験の提供に向けて、ネイティブなUIを提供することを重視し、マルチプラットフォーム を採用することになった。
中でもWebAPIとモバイルアプリの間のロジックの差異が大きな課題なのではと考えた。
解決しようとした課題
- ウェブとのデータモデルの定義がずれること
- 型の違い
- nullabilityの違い
- 新規のプロパティの付け忘れ
- モバイルアプリとして共通ロジックを保証
- バリデーション
- データモデルの一貫性
- 計算ロジック(税率など)
- 開発スピードの向上
- 両OSでの同ロジックを実装するコスト
- ウェブとの差異を埋め合わせしにくい
- 1箇所での修正を両アプリに反映しづらい
- 何か修正を入れる必要があった場合に修正箇所を一箇所にしたい
- モバイルチーム内の複数のアプリで共通でコードを使いまわしたい
マルチプラットフォームの選定と基準
上述した課題に対してはさまざまな解決策を模索した結果、最終的にKotlin Multiplatformに決めた。
理由としては...
- モバイル開発に特化したソリューションであった
- Androidと「同じ」Kotlin言語で書かれている
(Androidエンジニアにとっては自然に開発できる仮説) - iOSが使用されるSwiftのシンタックスに近しい
(iOSの開発者が慣れやすい仮説) - Kotlinという言語は、 大手のGoogleが採用しているため、サポートが信用できるはず
(KoltinはGoogleが提携している会社のJetBrainsにて開発されるのでIDEも信用できそう)
実践してから出てきた課題
どの解決策もフルネイティブな開発に比べて良し悪しがある。
例えば、マルチプラットフォームソリューションは、もともとネイティブと別の言語・フレームワークで作られており、それを完全に隠蔽することができず、漏れのある抽象化となる。当時はアルファ版であったので未知の世界に突入したことを覚悟してKotlinMutliPlatformを導入してみたのである。
おおよそ2年間Kotlin Multiplatformを、iOSとAndroidのアプリに導入・運用していくことでいくつかの課題が浮き彫りとなった。いうまでもないが、導入方法によって発生する問題はあるものの、選定したマルチプラットフォームソリューションにおいて普遍的な部分ないし固有な課題はある。
Kotlin Multiplatformを実践した結果:固有な課題
主な課題は、そのプラットフォームとネイティブの間に発生する仕組み(プログラミングモデル)のギャップとネイティヴへの橋渡しをする方法であった。特にiOSというかSwiftとKotlin Multiplatformの相性で改善の余地のあるところだが、Androidに関しても相違が少々あった。
iOS/Swiftに関する課題
- KotlinはClass指向言語のため、すべてのオブジェクトが参照型
- Swiftにある値型
struct
は再現できない - 簡単に
Equatable
やHashable
準拠しづらいためSwiftUI
に使いづらい - 一般的なSwift開発者が期待しているものと違うため、データの重複参照を気をつける必要はあった
- Swiftにある値型
- KotlinのSwiftへ橋渡しする方法としてはObj-Cを使用している
- そのためKotlinからSwiftで表現できないことがあるため、Swift開発者として馴染みのないことが度々あった
-
Int
以外のenum
を利用できない -
Codable
に準拠することができない -
Struct
のように軽量なコピーができない - マルチスレッドで
data class
をメモリ上安全に利用できない -
actor
やSwift Concurrencyから安全に利用できない - Kotlin側の例外を
try/catch
するのができない - Obj-Cの
.h
ファイル自体が機械的に生成されているため読みづらさがある - Kotlinの提供するInterfaceがSwiftの提供するProtocolに制限ある
- すべてObj-C ProtocolになるためSwift側の準拠は
struct
やNSObject
以外の使用できない - Kotlin側の
Interface
にInitializer
(コンストラクター)の定義ができない - Staticな関数の定義ができないため、Kotlinにある
Companion
オブジェクトを使用しなければならない
- すべてObj-C ProtocolになるためSwift側の準拠は
-
- そのためKotlinからSwiftで表現できないことがあるため、Swift開発者として馴染みのないことが度々あった
- 開発時の効率の問題
- 主な開発はAndroid Studioで行われるが、他方(iOS)のプロジェクトと独立している
- Kotlinの方でのソースコード変更はアプリの方で試し・確認するには、まるっとビルドとデプロイをしないといけない
- 1行の変更の検証は数分少なくともかかることがある
- 一方(例えばiOS)だけで検証しがちになる
(Androidビルドして確認するなら合わせて数十分かかることになりかねない) - 共通の基盤のため、iOSの方はAndroid側にどの影響を与えるかを常に考慮しないといけない
(もちろん逆のこともある)
- 主な開発はAndroid Studioで行われるが、他方(iOS)のプロジェクトと独立している
- ウェブとの差異を埋め合わせることができなかった
- iOSとAndroidの間にデータモデルの一貫性を保ったものの、ウェブとの差異は別の次元で存在した
- そもそもウェブが提供しているデータモデルが変更されたかは技術の問題というよりコミュニケーションの問題であった
- ウェブのビジネスロジックも、上述した開発フローのため、追いつくのに以前より努力が必要だった
- ウェブの方は完全に違う技術スタックを用いたかつ先行しているため、Kotlin Multiplatformの導入があってもなくても追従することは難易度がそもそも高かった。
- ここではスピードが発揮すべきだったものの、開発フローの課題のためより困難になった
最も重大な課題
最終的に、最も重大な課題としては、以下の三つを列挙する
- SwiftUIの相性が万全ではないこと
- 効率の悪い開発フローの課題
- ウェブとのビジネスロジック・データモデルとの追従の解決にはならなかった
※ 開発フローに関しては、最終的に完全に自動化したとしても、ネイティブで修正・開発する方が早かった
振り返りと今後の道
最終的に、我々が解決しようとした課題は技術そのものより、開発プロセスやコミュニケーションの課題であった という結論に至った。
統合型開発とサービス
統合型サービスを提供するためには、モバイルアプリのネイティブ側で、プラットフォームをまたがる共通基盤の実現すると同時に、チーム間のコミュニケーションや同時計画・同時開発が必要である。
例えば、双方(Android/iOS)の間のビジネスロジックの課題は、ドキュメンテーションとチームコミュニケーションだったとした場合に、コミュニケーションを密にとりながら双方でPRのレビューを実施することも有効である。
他にも、API用データモデルの定義がウェブとずれてしまうという課題は、ウェブの方々との連携が不十分だったのではと考えた。
これも技術のソリューションでは解決できないもので、Android/iOS間の設計ずれと同じように、同時に計画したり開発したりすることで解決できる。その上、同じバックエンド(API)を使用することでビジネスロジック(例えばBFF)を共通化することも可能なのである。ウェブ側が先行していても、モバイルアプリは同API・バックエンドを利用し、同じ体験の提供が可能で、馴染みのあるネイティブ開発をよりスピーディーに実現できる。
freee における統合体験を提供
freeeでは「統合体験」を重要視している。
統合体験とは、会計アプリでしかできない、人事労務アプリでしかできないなど、望ましくない体験をなくし、どのサービスからでも円滑にユーザーが必要とする体験を提供することである。
例えば、企業のマネージャーの立場なら、人事労務や会計などそれぞれで承認依頼が飛んでくるが、承認作業はそれぞれサービスで必要となる。従業員なら、歴史的な背景から会計に経費申請の機能が組み込まれているため、人事労務アプリとは別に会計アプリを入れる必要がある。freeeではこのような複数のアプリにまたがって利用する機能を一箇所に集約し統合的な体験を提供したいと考えている。
これからは「統合型」ソフトをさらに実現するために、技術だけでなく、組織もチームも統合型を目指し、チームワークを発揮しながらプロセスの改善を行うべきだと考えている。
道が開ける
iOS・SwiftとKotlinMutliplatformでは技術的な課題を完全に解決することができなかったものの、導入してみたことで新たな課題に気づくことができ、今まで以上にチームワークを強め統合的なチームを組めるようになった。
これからはさらに良いユーザ体験、統合型体験を提供できるよう努めます!
iOSDCのトークンは#統合
です!