Luup iOSアプリ開発の現状と課題
はじめましてLuupのiOSエンジニア大瀧です。
今回はiOSチーム初稿ということでLUUP iOSアプリの開発体制や採用技術、現状の課題について書いていきます。
開発体制
LUUPアプリのiOSチームは現在正社員2名で業務委託のメンバーが4人となっています。
技術顧問として@tarunonさんに在籍いただいていてiOS開発の技術的なリードやSPM対応&マルチモジュール化など実際に手を動かしていただいてもらっています。隔週で@tarunonさんに質問会を設けていただいていて社員としては最高の福利厚生ですね。
アーキテクチャ
開発初期からCleanArchitecture+MVPでの実装となっています。サービスが徐々に大きくなった今でも特に変わることなく継続しています。
後の章でも触れますが、マルチモジュール構成を採用したことにより特にCleanArchitectureの部分で依存関係が明確になり、より厳格なルールの中でコーディングができるようになっていってます。
とはいえLUUPアプリの性質上、複雑なビジネスロジックゆえに徐々にFatになっていくMap画面に対しては課題感を感じています。
位置情報更新をトリガーとした処理にはサービスエリア外判定やキックボードの手押しゾーンの判定、ライド開始フローではデバイスや返却ポートを選択するユーザーへのナビゲーションUI、その他にGoogleMapの制御など機能が追加するたびMap画面が肥大化していくのがLUUPアプリの特徴です。
ビジネスロジックの肥大化に対してはピンマーカーを管理するPresenterやエリアを管理するPresenterなど複数のPresenterを用いることで責務を分割して対応しています。
UIフレームワーク
SwiftUIに関してはサポートバージョンがiOS14+になったタイミングで積極的に導入を進めています。
将来的にはできる限りSwiftUIで実装していくのが目標ですが、現状ではSwiftUIによる画面遷移は既存のUIKitとの相性が悪い点やCombineを使ったUIバインディングは既存のアーキテクチャと乖離するため、画面単位での実装はUIViewControllerで行なっています。
ダイアログやモーダルの中身、CompositionalLayoutのセルといったコンポーネント単位でSwiftUIを導入しています。
CompositionalLayoutのCell部分のSwiftUI化に関してですが、iOS16からはUIHostingConfigurationが利用できますが、もちろんiOS16未満では利用できないのでTimeTreeさんの記事を参考にしています。
先ほど画面単位での実装ではUIViewControllerを利用していると書きましたが、単純にStoryboardを用いてのUI構築をしているのではなく、UIKitでもXcodePreviewsを導入し、置き換えていってます。
Storyboardで構築された画面ではコンフリクト解消やコードの再利用の点に難があったり、AppleがStoryboardのメンテナンスに積極的ではないように見えていて不安があるので、こちらの取り組みを行なっています。ほとんどのUIはStackViewの組み合わせで構築できるのでPreviewさえできればSwiftUIでのUI構築と遜色なく開発ができています。
UIKitでのXcodePreviewsの導入に関してはメルペイiOSチームの記事を参考にしています
前の章で触れたMap画面はStoryboardで実装されていて、Autolayoutが大量に貼り付けられていて非常にメンテナンスコストがかかる画面となってしまっています。徐々にStackViewで入れ子にしていきAutolayoutを剥がすことはできていますが、Map画面のSwiftUI化はかなり試練の道となりそうです。
SPM
LUUPアプリでは半年ほど前にマルチモジュールでの開発へ移行して行くことを目的に、CocoapodsからSwiftPackageManagerへパッケージマネージャを変更しました。
一部のSPM未対応のライブラリに関してはCarthageを利用してxcframework化して対応しています。
SPM化に加えてビルド時間短縮やレイヤ間の依存関係を明確にする意味でもマルチモジュール構成を採用しています。
モジュール化の進め方はレイヤー毎にモジュールを作成し、機能単位でファイルを移行していく形で進めています。機能ごとのモジュール化に加えて、async/await対応、SwiftUI化や脱Storyboard、各Prensenterクラスのテストコード追加などを並行して進めています。
アプリ自体は2週間に1度のペースでリリースを行なっているので安全面とスピード感の両立を考えた結果、各リリースで1機能を目標にモジュール化を進めています。
既に知見を広めてくださっている方もいますが、XcodeのバグでSPM✖︎マルチモジュール構成のプロジェクトで別ターゲットのリソースにアクセスするとバンドルが読み込めずにPreview環境でクラッシュする現象が起きています。
各モジュールごとにバンドルを定義してあげるなどいくつかワークアラウンドがあるようなので検討してみようと思います。
CI・CD
CIにはGithub Actionsを利用しています。SwiftLintによるLint、Formatを行なっていて、Dangerにより各PRに対してLint warningsをコメントで通知するようにしています。今後はテストのカバレッジレポートの表示やPeripheryを走らせられると良いなと思っています。
CDにはBitriseを利用しています。タグPushのタイミングでmasterがビルドされAppStoreConnectまで配信するようになっています。
サービスの安定性や信頼性への取り組み
FirebaseCrashlyticsを利用したクラッシュフリーレートの監視やFirebasePerformanceMonitoringで画面の描画時間やネットワークレイテンシを定量的に追っていく取り組みを行なっています。
定量的することでリファクタリングにおける効果を可視化して結果的に開発者のモチベーションの向上にもつながっています。
最後に
ここ数年で目覚ましく変わるiOSアプリ開発状況ですが、Luupではなんとか追従できているのではないかと思っています。iOSエンジニアにとってはモダンな技術の元で開発できる上に、Luup特有の複雑なビズロジックをコードに落とし込む作業はとてもエキサイティングです。とはいえ、まだまだ課題は山積みなのでこれからも改善を続けていきます!
Luupでは現在iOSエンジニア採用を積極的に行っていますので、課題と挑戦がいっぱいのLuupでのiOS開発に少しでも興味を持っていただけた方は、是非お気軽にご連絡ください!
Discussion