> For the complete documentation index, see [llms.txt](https://docs.wantedly.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.wantedly.dev/fields/apps/mobile-native-app/mobile-ios.md).

# iOS

想定読者:

* iOS アプリの開発を担当するモバイルエンジニア
* Android を主とし、iOS 側の構成を把握したいモバイルエンジニア

本章では Wantedly の iOS アプリのアーキテクチャを扱います。 ここでは iOS ネイティブに閉じた話を対象とし、個別の実装やコードの詳細には踏み込まず、全体像を素早くつかめることを目的とします。

モバイルアプリの開発では、新しい技術の登場やプロダクトの課題の変化にあわせて、アーキテクチャは必然的に世代交代していきます。 全面的に作り直して一度きれいにしても、その効果は長続きせず、いずれまた段階的な改善を迫られます。

だからこそ、個々のアーキテクチャや実装そのものよりも、その選択の背後にある思想・戦略を理解することが重要です。 本章では Wantedly の iOS アプリの構成を具体的に示しながら、なぜその形を選んでいるのかという考え方を中心にまとめます。

## 概要 — 設計思想

Wantedly の iOS アプリは次の原則に基づいて設計されています。

* 段階的リニューアル: 全面刷新は行わず、画面・機能単位で少しずつ置き換える
* 単方向データフロー: 状態の流れを一方向に保ち、挙動を予測可能にする
* Single Source of Truth: 多くのデータは端末内データベースを情報源とし、画面はその変更を購読する
* モジュール分割: 役割と機能でモジュールを分割し、規模の拡大に耐える
* ロジックの共有: ビジネスロジックを Kotlin Multiplatform (KMP) で Android と共通化する
* 移行を前提とした運用: 新規実装は新しい方式に寄せ、古い方式の新規追加を避ける

iOS は 2018 年に全面リニューアルを経験しています。 このときはリソースの大量投入によって、プロダクト開発が長期間止まりました。 さらにその後、SwiftUI のような新技術が登場したことで、結局は段階的な改善が必要になりました。 全面刷新の効果は一時的なものにとどまったわけです。 この経験から、現在は大規模な一括変更を避け、画面・機能単位での段階的な改善を続ける方針を取っています。 この「段階的である」という制約が、以降で述べる多くの設計判断の背景にあります。

## アーキテクチャ

### 全体構造

各画面を薄く保ち、状態管理とビジネスロジックを画面の外へ出すことで、画面単位で少しずつ移行できるようにしています。

* 画面遷移のロジックは後述の Coordinator が担い、画面 (ViewController / SwiftUI View) は表示に専念します。
* 画面の状態は、1 画面につき 1 つの Reactor が管理します。
* 多くの画面では、Reactor が端末内データベースの変更を購読し、変更に応じて UI を更新します。
* Reactor は KMP 側で実装され、Android とロジックを共有します。詳細は [KMP の章](https://docs.wantedly.dev/fields/apps/mobile-kmp)で扱います。

### レイヤーと責務

上位から下位への単方向の依存で、次のレイヤーに分かれます。

* UI: 画面の描画と入力の受け取り (UIViewController / SwiftUI View / Coordinator)
* Presentation (Reactor): UI 状態の管理と、ユースケースの呼び出し
* UseCase: アプリ固有のビジネスルール
* Repository: データソースの抽象化
* DataSource: API やデータベースとの実際のやり取り
* Entity: ドメインオブジェクトの定義

Reactor は Repository を直接呼ばず、必ず UseCase を経由します。 ビジネスロジックを UseCase に閉じ込めることで、再利用しやすく、テスト時に差し替えやすくなります。

### 状態管理の世代

単方向データフローという思想は共通しつつ、それを実現する実装には新旧 2 つの世代があります。 新旧が併存しているのは、全面刷新を避け、画面単位で段階的に移行しているためです。

| 世代 | UI                    | 状態管理          | 非同期     |
| -- | --------------------- | ------------- | ------- |
| 旧  | UIKit (No Storyboard) | ReactorKit    | RxSwift |
| 新  | SwiftUI               | KMP の Reactor | RxSwift |

* 画面ごとに UI 実装と状態管理を選べるため、UIKit の画面を SwiftUI と KMP の Reactor へ、画面単位で少しずつ移行できます。
* 新しく作る機能は、SwiftUI と KMP の Reactor の組み合わせを採用しています。旧世代の ReactorKit から KMP の Reactor へ移したのは、Android とビジネスロジックを共有するためです。
* 非同期処理は新旧どちらの世代も RxSwift が中心で、Swift Concurrency へ全面的には移行していません。

### 画面遷移

画面遷移には Coordinator パターンを用います。 Coordinator パターンは、画面遷移のロジックを画面 (ViewController / View) から切り離し、専用のオブジェクトに集約する仕組みです。 画面が「次にどこへ遷移するか」を直接知らなくてよくなるため、画面の再利用性が高まり、遷移ロジックのテストや差し替えが容易になります。

* 各画面・各フローに対応する Coordinator を用意し、アプリ起動時の起点となる Coordinator から順に構築します。
* モジュールをまたぐ遷移は、遷移の生成を一箇所に集約する窓口を通します。機能モジュール同士が互いに直接依存せずに他機能の画面へ遷移できるようにするためです。
* プッシュ通知や外部リンクなど、アプリ外部からの遷移は URL を遷移リクエストに解決して扱います。

### DI（依存性注入）

依存性注入は、オブジェクトが必要とする依存を外から渡す仕組みです。 結合度を下げて実装を差し替えやすくし、テストを書きやすくします。

* 依存性注入には Swinject を用います。
* 依存の登録は役割ごとに複数の Assembly に分けて定義し、起動時に Assembler で 1 つのコンテナにまとめます。テストやスタブ用に Assembly を丸ごと差し替えられます。

### UI 実装の方針

* UI は SwiftUI へ移行中です。宣言的 UI により画面実装を簡潔にでき、Apple 標準の方向性にも追随できるためです。新規の画面は SwiftUI で実装し、UIKit 画面の新規追加は避けます。
* 独自実装ではなく、デザインシステムのコンポーネントを優先して使います。デザインシステムの詳細は「デザインシステム」の章を参照してください。
* 既存の UIKit ベースの画面も、修正の機会にあわせて SwiftUI へ置き換えていきます。

## モジュール構成

モジュールを分割する狙いは、変更の影響範囲を絞ってビルド時間を抑え、依存を整理して全体の見通しを良くすることです。

* アプリのコードは、ローカルの Swift Package である `VisitPackage` に集約し、機能とレイヤーでモジュールを分割しています。
* KMP の共有コードは、1 つのモジュールとして取り込みます。KMP 自体は内部的にマルチモジュールですが、iOS からは 1 つのまとまったモジュールとして扱われる性質があるためです。
* サードパーティ依存は Swift Package Manager (SPM) に一本化しています。
* Xcode プロジェクトのファイルは XcodeGen で生成し、プロジェクト設定の差分をレビューしやすくしています。

## テスト戦略

テストはテスティングピラミッドの考え方に沿って構成します。 実行が速く安定したユニットテストを土台として厚く積み、実行コストが高く壊れやすい UI テストは上位に絞ります。 フィードバックの速さと安定性を保つためです。

* ユニットテスト（土台）: Quick / Nimble で記述します。
* モック: Cuckoo を用います。
* UI / E2E テスト（上位）: XCUITest に加え、ノーコードの E2E テスト自動化サービス MagicPod を用います。

自動テストに加えて、実機での動作確認も行っています。

## 歴史

iOS の実装は、おおまかに次の順で世代を重ねてきました。

1. Swift + UIKit による ReactorKit（2018 年の全面リニューアル）
2. KMP の Reactor（2021 年、Android とのロジック共有）
3. SwiftUI（2022 年、宣言的 UI への移行）

詳細な世代の遷移は [モバイルネイティブアプリについて](https://docs.wantedly.dev/fields/apps/mobile-native-app) の章に譲ります。

## 話を聞きに行きたい

* Slack: [#mobile\_chapter](https://wantedly.slack.com/archives/C014VN3KKB9)

## もっと知りたい

* [KMP の章](https://docs.wantedly.dev/fields/apps/mobile-kmp)（本 Handbook 内。ロジック共有と Reactor の詳細）
* [モバイルネイティブアプリについて](https://docs.wantedly.dev/fields/apps/mobile-native-app) の章（本 Handbook 内。各 OS の世代の全体像）
* [wantedly/visit-app](https://github.com/wantedly/visit-app) リポジトリ (internal。iOS のアーキテクチャや SwiftUI の実装規約を含む)
