コンテンツにスキップ

iOS Architecture

目的

ios/Rikako のディレクトリ構成と責務を整理するためのメモです。

現在の方針は、画面から直接 API を呼ばず、次の依存方向で組むことです。

Screen (View)
  -> ViewModel
    -> UseCase
      -> Repository
        -> Infrastructure

ディレクトリ構成

ios/Rikako/
  AppContainer.swift
  AppState.swift
  RikakoApp.swift

  Domain/
    Entity/
    Repository/
    UseCase/

  Data/
    Remote/
      Request/
      Response/
    Repository/

  Infrastructure/
    Identity/
    Network/

  Screen/
    Root/
    Main/
    Onboarding/
    StudyHome/
    StudyRecord/
    MyPage/
    Settings/

補助的に、まだ feature ディレクトリへ移し切っていない画面もあります。

  • Screen/QuizView.swift
  • Screen/ResultView.swift
  • Screen/ProfileView.swift
  • Screen/WrongAnswersView.swift
  • Screen/NotificationsView.swift
  • Screen/HelpAndSupportView.swift
  • Screen/LegacyTopView.swift
  • Screen/LegacyCategoryViews.swift
  • Screen/WorkbookListView.swift
  • Screen/WorkbookDetailView.swift

各レイヤの責務

Screen

SwiftUI の View を置く層です。

  • 表示とユーザー操作の受け取りに集中する
  • 初回読み込みは task などから ViewModel を呼ぶ
  • API や Repository を直接触らない

feature ごとにディレクトリを切り、必要なら同階層に ViewModel を置きます。

例:

ViewModel

画面状態と画面単位の処理を持つ層です。

  • isLoading
  • errorMessage
  • fetched data
  • 画面表示向けの整形
  • 初回 fetch

ViewModel は Observation@Observable を使います。

例:

Domain/Entity

アプリの中心概念を置く層です。

  • Question
  • Workbook
  • Category
  • AnswerItem

View 都合でも API 都合でもなく、アプリで扱う意味のあるデータを置きます。

例:

Domain/UseCase

画面がやりたいことを明示する層です。

  • 問題集一覧を取る
  • 問題集詳細を取る
  • カテゴリ一覧を取る
  • 回答を送る

複数画面で共通化したい操作はここに置きます。

例:

Domain/Repository

データ取得の抽象です。

ViewModel / UseCase から見ると、どこからデータが来るかを隠します。

例:

Data/Remote

API や配信 JSON の request/response モデルを置く層です。

DTO ではなく、役割が分かるように Request / Response で分けています。

例:

Data/Repository

Repository protocol の実装です。

現在は remote から取得する実装を置いています。

例:

Infrastructure

HTTP や device identity など、外部 I/O の詳細を置く層です。

例:

AppContainer

依存を束ねる composition root です。

例:

ここで Repository 実装を組み立てて UseCase を作ります。

AppState

画面横断の小さい shared state だけを持つ層です。

現在保持しているもの:

  • オンボーディング完了状態
  • ログイン状態
  • 選択中の問題集 ID
  • 学習結果の簡易サマリ
  • 間違えた問題

例:

画面固有の fetched data や loading 状態は AppState に置かず、ViewModel に置きます。

Root からの流れ

現在の画面遷移の起点は RootView です。

flowchart TD
    A[RootView] --> B{hasCompletedOnboarding?}
    B -- No --> C[OnboardingView]
    B -- Yes --> D[MainView]
    D --> E[StudyHomeView]

詳細は navigation.md を参照してください。

モックが残っている場所

現状、問題系の取得は実 API / 実 JSON を使っていますが、周辺機能は仮実装が残っています。

実データ

  • 問題集一覧
  • 問題集詳細
  • カテゴリ一覧
  • カテゴリ詳細
  • 回答送信 API

仮実装

運用ルール

  • View から UseCaseRepository を直接呼ばない
  • 初回 fetch は ViewModel で行う
  • 画面横断状態だけを AppState に置く
  • API request/response は Data/Remote/RequestData/Remote/Response に置く
  • アプリの中心データは Domain/Entity に置く
  • 新しい画面は Screen/<Feature>/ を作って、その中に ViewViewModel を置く

今後の整理候補

  • Screen/ 直下の画面を feature ディレクトリへ移す
  • Remote/ResponseEntity の mapper を明示的に分ける
  • WrongAnswersResult も ViewModel 経由へ寄せる
  • 学習記録やお知らせを仮実装から本実装へ置き換える