iOSアプリ 新アーキテクチャ概要編 〜レガシー脱却🛠️ 持続可能でテストしやすい開発へ〜

1. なぜやるのか?
| 観点 | 旧アーキテクチャの課題 | 新アーキテクチャでの改善点 |
| テスト性 | UIとビジネスロジックが密結合で単体テストが困難 | UIとビジネスロジックの分離でテストが容易に |
| 保守性 |
コードの責務が不明確で修正の影響範囲が予測困難 | 各層の役割が明確で修正影響範囲を限定可能 |
| 開発効率 | コードの再利用が困難でチーム分業が複雑 | 機能単位でのチーム分業が可能 |
| 並行処理 | データ競合のリスクとメインスレッドブロッキング | Actorによるデータ競合の防止とUI応答性向上 |
2. 全体像を理解する
| カテゴリ | 要素 | 役割 | 目的 |
| アーキテクチャ設計 (骨組み) |
Clean Architecture
|
ビジネスロジックを外側の実装から守る層構造
|
保守性・テスト容易性の向上
|
|
Vertical Slice Architecture
|
機能単位での開発を可能にする分割方法
|
機能間の独立性確保・開発効率の向上
|
|
| 設計手法 (工法) |
Domain-Driven Design(DDD)
|
ビジネス要件中心の設計アプローチ
|
ビジネス要件と実装のギャップを埋めるため
|
|
UDF
|
データの流れを一方向にする設計パターン
|
コードの可読性・保守性の向上
|
|
|
実装要素・技術
(道具・手段)
|
Swift Concurrency
Strict Concurrency |
非同期・並行処理の実装(async/await, Actor)+コンパイル時のデータ競合安全性の保証
|
非同期処理と並行処理をより安全かつ簡潔に記述するため
|
| その他の実装要素(Store, UseCase,Repository, Entity/Modelなど) |
各レイヤーの責務を担う構成要素
|
関心事の明確な分離と
レイヤー間の一貫した通信の実現 |
|
| その他の実装技術 (Protocol, DI など) |
インターフェースによる抽象化と コンポーネント間の依存関係管理 |
モジュール間の疎結合化と テスト容易性の向上 |
2.1 Clean Architecture(クリーンアーキテクチャ)
- 簡単な説明:「レイヤー(層)で分けて、内側のビジネスロジックを外側の実装から守る」設計方法
💡ビジネスロジックを外側の実装詳細から守ることで、UIやデータベースなど外側の技術が変わっても、核となるビジネスロジックは安定して再利用できるため、長期的な保守性が向上します。
- 特徴
- ✅ 層ごとに役割が明確
- ✅ 内側のコードは外側に依存しない(重要)
- ✅ テストがしやすい
- ⚠️ 初期学習コストが高め
- 構造イメージ
- 依存の方向(重要)
- ✅ 外側 → 内側への依存のみ許可
- ❌ 内側 → 外側への依存は禁止
- 例:Domain層はUIKitやSwiftUIをimportしない
- 依存ルール
Feature Layer ← UI層 ↓ 依存可能:Core/Domain のみ Core/Domain Layer ← ドメイン層 ↓ 依存可能 Core/Data Layer ← データ層 ↓ 依存可能 Core/Infrastructure Layer ← インフラ層
2.2 Vertical Slice Architecture(機能単位での独立性)
- 簡単な説明:「機能ごとに切り分けて、必要なものを全部まとめる」設計方法
- 特徴
- ✅ 機能単位で完結している
- ✅ 変更が容易(1つの機能の改修が他に影響しない)
- ✅ コードの見つけやすさ◎
- ⚠️ 機能間の共有コードは別途Core層で管理
- 構造イメージ
- メリット
- 機能追加時に既存機能への影響が少ない
- 機能削除が容易(フォルダごと削除可能)
- チーム分業がしやすい(機能単位でタスク分割)
2.3 UDF(単方向データフロー)
- 簡単な説明:データの流れを「一方向」に制限することで、コードの可読性と保守性を高める設計パターン
- UDFの原則
- 状態は一箇所で管理する
- Viewから直接状態を変更しない
- 状態更新はイベント経由で行う
- 構造イメージ
- なぜUDFを使うのか?
- 💡データの流れが一方向でシンプルな状態管理のため、状態更新の追跡(デバッグ)が容易
3. Swift Concurrencyを知る
3.1 なぜSwift Concurrencyを使うのか?
| 観点 | 従来の方法 | Swift Concurrency |
| コードの可読性 | コールバックが入れ子になり、読みにくい | 上から下へ順番に読める、自然なコードになる |
| エラー処理 | 各コールバックでエラーを個別に処理する必要がある | try/catch で統一的に処理できる |
| スレッドの安全性 | 複数スレッドからのアクセスで予期しないバグが発生しやすい | actor により自動的にスレッドセーフになる |
| 処理のキャンセル | 処理の途中キャンセルの実装が複雑 | 処理のキャンセルが自動的に伝播する |
3.2 主要な要素と使用場所
| 要素 | 何をするもの? | どこで使う? |
| async/await | 非同期処理を順番に書けるようにする | UseCase, Repository |
| actor | データを複数スレッドから安全に扱う | Store(状態管理) |
| @MainActor | UI更新を安全に行う | View |
| AsyncStream | イベントを継続的に通知する | Store → View |
| Sendable | スレッド間で安全にデータを渡す | Entity, AsyncStreamで流れるデータ(Event) |
3.3 各要素の簡単な説明
| 従来 | Swift Concurrency |
| コールバックが入れ子になる | 上から下へ順番に書ける |
| エラー処理がバラバラ | try/catch で統一 |
| 従来 | Swift Concurrency |
| 手動でロック処理が必要 | actor が自動でガードしてくれる |
| ロック忘れでバグ発生 | コンパイラがチェックしてくれる |
| 付ける場所 | 理由 |
| View | UIの状態管理・画面表示を更新するため |
- 使用場面:
- Storeの状態が変わったことをViewに通知する
- リアルタイムで画面を更新したい場合
- ポイント:
- structやenumは基本的に自動でSendable
- actorやAsyncStreamと一緒に使うデータに必要
3.4 まとめ:主要要素のイメージ図
- 構造イメージ
💡 ポイント
Swift Concurrencyは「より安全に、より読みやすく、より保守しやすい」コードを書くための技術です。最初は新しい概念が多くて難しく感じるかもしれませんが、慣れると従来のコールバック方式より圧倒的に書きやすくなります。
4. Strict Concurrencyを知る
4.1 Strict Concurrencyとは?
💡 ポイント
従来はデータ競合のバグが実行時に発生して初めて気づくことが多かったですが、Strict Concurrencyによりコンパイル時に検出できるようになります。つまり、アプリを動かす前にバグを防げるということです。
4.2 主なチェック項目
| チェック項目 | 内容 | よく見るエラー例 |
|
Sendableチェック
|
スレッド間で渡すデータが安全か | Type 'XX' does not conform to 'Sendable' |
|
Actor隔離チェック
|
actorのデータに正しくアクセスしているか | Actor-isolated property 'xx' can not be referenced from... |
|
グローバル変数チェック
|
グローバルな可変状態がないか | Global variable 'xx' is not concurrency-safe |
5. まとめ
データフロー(ユーザー操作から結果表示まで)
- シーケンス図イメージ




