Development iOS

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

1. なぜやるのか?

現在のiOSプロジェクトのアーキテクチャは長年の開発で複雑化し、テスト性や保守性などの面で課題が出てきていました。
これらの課題を解決し、より持続可能な開発を実現するため、新アーキテクチャへの移行を進めています。

改善される点

観点 旧アーキテクチャの課題 新アーキテクチャでの改善点
テスト性 UIとビジネスロジックが密結合で単体テストが困難 UIとビジネスロジックの分離でテストが容易に
保守性
コードの責務が不明確で修正の影響範囲が予測困難 各層の役割が明確で修正影響範囲を限定可能
開発効率 コードの再利用が困難でチーム分業が複雑 機能単位でのチーム分業が可能
並行処理 データ競合のリスクとメインスレッドブロッキング Actorによるデータ競合の防止とUI応答性向上

2. 全体像を理解する

新アーキテクチャは、以下の設計概念などを組み合わせ、保守性が高く、テストがしやすい構造を実現しています。
特に、骨組みではClean/ Vertical Slice Architectureを組み合わせることで、
責務の分離と開発効率向上を両立している点がポイントです。
この章では特に重要な要素に焦点を当てて説明します。

カテゴリ 要素 役割 目的
アーキテクチャ設計
(骨組み)
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を知る

新アーキテクチャでは、非同期処理と並行処理に Swift Concurrency を採用しています。
この章では、なぜSwift Concurrencyを使うのか、そのメリットと主要な要素を説明します。
コンパイラによる安全性の強制(Strict Concurrency)については次の章で説明します。

3.1 なぜSwift Concurrencyを使うのか?

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 各要素の簡単な説明

【async/await】
役割:非同期処理(API通信など待ちが発生する処理)をシンプルに書ける仕組み

従来 Swift Concurrency
コールバックが入れ子になる 上から下へ順番に書ける
エラー処理がバラバラ try/catch で統一

使用場所:UseCase、Repository(API通信やデータベースアクセス)

【actor】
役割:複数の場所から同時にアクセスされても、データが壊れないようにする仕組み

従来 Swift Concurrency
手動でロック処理が必要 actor が自動でガードしてくれる
ロック忘れでバグ発生 コンパイラがチェックしてくれる

使用場所:Store(複数画面で共有する状態の管理)

【@MainActor】
役割:UI更新が必ずメインスレッドで行われることを保証する仕組み

付ける場所 理由
View UIの状態管理・画面表示を更新するため

ポイント:UseCaseやRepositoryには付けない(UIに関係ないため)

【AsyncStream】
役割:イベントを継続的に流し続ける仕組み(川のようなイメージ)
  • 使用場面
    • Storeの状態が変わったことをViewに通知する
    • リアルタイムで画面を更新したい場合

【Sendable】
役割:「このデータはスレッド間で安全に渡せます」という証明
  • ポイント
    • structやenumは基本的に自動でSendable
    • actorやAsyncStreamと一緒に使うデータに必要

3.4 まとめ:主要要素のイメージ図

この図はSwift Concurrencyの主要要素を視覚化しています。

  • 構造イメージ

💡 ポイント

Swift Concurrencyは「より安全に、より読みやすく、より保守しやすい」コードを書くための技術です。最初は新しい概念が多くて難しく感じるかもしれませんが、慣れると従来のコールバック方式より圧倒的に書きやすくなります。

4. Strict Concurrencyを知る

この章ではSwift Concurrencyの安全性をコンパイラが強制する Strict Concurrency について説明します。

4.1 Strict Concurrencyとは?

Strict Concurrencyとは、Swift 6でデフォルトとなるコンパイル時のデータ競合チェック機能です。
actor、Sendable、@MainActorなどの仕組みを、コンパイラが正しく使えているかチェックし、強制してくれます。

💡 ポイント

従来はデータ競合のバグが実行時に発生して初めて気づくことが多かったですが、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. まとめ

データフロー(ユーザー操作から結果表示まで)

以下の図は、新アーキテクチャにおけるユーザー操作から結果表示までの典型的なデータの流れを示しています。

  • シーケンス図イメージ

Latest最新記事

Popular人気の記事