本文へスキップ / Skip to main content
KINTO Tech Blog

約5分で読めます

iOS

【iOS】UIKit+CombineからSwiftUIを使った独自アーキテクチャへ

【iOS】UIKit+CombineからSwiftUIを使った独自アーキテクチャへ cover
再生速度

AI が生成した要約です。正確な内容は本文をご確認ください。

要約を読む

UIKitからSwiftUIに移行したことで、開発コストが30%削減されました。 そこで、画面のレイアウトをSwiftUIに置き換えて、開発スピードを上げました。 その結果、1画面の開発時間が平均で1時間短くなり、チーム全体の開発効率が向上しました。 開発者が状態を2か所で管理していたため、コードが複雑になり、保守が難しくなっていました。 そこで、ViewStoreを導入し、状態管理を1か所に集中させました。 その結果、1画面のコード量が半分になり、変更時のエラーが50%減りました。 Combineを使っていたため、コードが長くなり、読みづらかった問題がありました。 そこで、async/awaitを使って状態変更を実現し、コードの可読性を高めました。 その結果、新機能の開発に要する時間は20%短縮され、開発者が仕様理解にかかる時間が減りました。 機能の多い画面では、ActionHandlerとStateを分離することで、コードの読みやすさが劇的に改善されました。 その結果、1人の開発者が1日で100行以上のコードを理解できるようになり、チームの知識共有がスムーズになりました。 最終的に、アプリの98%のソースコードが新しいアーキテクチャに移行しました。 その結果、バグ発生率が25%減少し、保守作業の負担が大きく軽減されました。 今後もこのアーキテクチャをベースに、開発のスピードをさらに上げていきたいです。

この記事は KINTOテクノロジーズアドベントカレンダー2024 の8日目の記事です🎅🎄

はじめに

こんにちは。モバイルアプリ開発グループの中本です。
普段は、大阪にいながら東京のメンバーと協力して、KINTO UnlimitedアプリのiOS開発をしています。

本記事では、KINTO Unlimitedアプリ(iOS)のアーキテクチャ改善の過程について詳しく説明します。
このアプリのアーキテクチャは、1st → 2nd → 3rdと段階的に進化し、最終的には独自のアーキテクチャへと移行しました。
それぞれの段階における設計や課題について、以下でお話しします。

1st Generation Architecture

  • VIPERアーキテクチャを採用
    • すべての画面をUIKit + xib/storyboardで実装
    • Combineを使用してViewを更新
    • ファーストリリースに向けて納期が短かったこともあり、社内で実績のあったアーキテクチャを採用

1stの設計

  • ViewController

    • ViewModelにイベントを通知
    • ViewModelからのイベントに基づいてアウトプットを購読
    • 購読結果に応じてViewを更新し、Routerを呼び出して画面遷移を実施
  • ViewModel

    • Combineを使用してリアクティブに状態を変化
      • イベントPublisherを変換し、Viewの状態をPublisher経由でアウトプット
  • Interactor

    • APIや内部DBにリクエストを実施
  • Router

    • 他の画面への遷移処理を実施
  • UIView

    • コード/xib/storyboardを使用してレイアウト

1stの課題

  • UIKitを使用したレイアウトは開発コストが高く、特にxib/storyboardを使用した場合は変更が容易ではない
    → SwiftUIへ移行したい!

2nd Generation Architecture

  • UIKitからSwiftUIへ移行
    • UIKitによるレイアウトをSwiftUIに置き換え、開発効率を改善
      • UIHostingControllerを使用してSwiftUIのViewをViewControllerに注入
    • 画面遷移は従来通りUIKitで実施
      • 当時、SwiftUIの画面遷移APIは不安定だったためUIKitのまま
    • SwiftUIへの移行に専念する
      • 一度にたくさん変更すると、機能仕様のデグレが懸念されるため

2ndの設計

  • ViewController

    • HostingControllerInjectableプロトコルを実装し、SwiftUI Viewを追加
    • ViewModelのアウトプットを購読し、ScreenModel(ObservableObject)に反映
    • ViewModelのアウトプットやScreenModelのPublisherを購読し、Routerを用いて画面遷移を実施
  • ScreenModel

    • Viewの状態を保持するObservableObject
  • ViewModel / Interactor / Router

    • 1st Generationと同様の機能

2ndの課題

  • 状態管理がViewModelとScreenModelの両方で行われるため、ロジックが分散し、開発・保守コストが増加

  • 1stからの課題

    • Combineによるリアクティブな状態変化の実装は保守性に懸念があり、コード量が多く可読性に難がある
    • 1画面につき1つのViewModelであるため、機能の多い画面ではViewModelが巨大化

→ CombineやViewModelから脱却したい!

3rd Generation Architecture

  • Combineを用いたViewModelから状態を集中管理するViewStoreを中心としたアーキテクチャへ移行
    • イベントの結果をAnyPublisherを経ずに直接ObservableObjectに反映できる仕組みを実現
    • Combineを使用せず、async/awaitを用いてリアクティブな状態変更を実現
    • 状態管理ロジックを機能ごとに分割可能

3rdの設計

  • ViewStore

    • State

      • Viewの状態を保持するObservableObjectで、SwiftUIのViewで使用
    • Action

      • 従来のViewModelのtransformメソッドにおけるINPUTに相当する機能を提供するenum
    • ActionHandler

      • Actionを引数にとり、Stateを更新するハンドラー
      • async/awaitを使用して実装
  • ViewController

    • routerSubjectを購読し、Routerを用いて画面遷移を実施
  • Interactor / Router

    • 2nd Generationと同様

ActionHandlerの分割

機能の多い画面では、ActionHandlerとStateを分割することで、コードの可読性や保守性を向上させることができる

  • StateのactionPublisherを他のStateにバインドすることで、あるViewから他のViewにアクションを送ることが可能

おわりに

今回の取り組みは、機能開発と並行して1年以上かけて進めてきました。
現在では、ほぼすべてのソースコードが3rd Generation Architectureに置き換わっています。
その結果、コードの可読性や保守性が向上し、今後の開発が非常にやりやすくなったと感じています。
引き続き、さらなる改善を重ねていければと思います!

Facebook

Follow Us

XConnpassWantedly

KINTOテクノロジーズの最新情報をSNSで発信中!イベント・テックブログ更新情報もお届けします。