「iOSアプリ設計パターン入門」から学ぶ、設計の基礎

Table of Contents

Details

2種類のアーキテクチャ

  • GUIアーキテクチャ

  • システムアーキテクチャ

  • Presentation Domain Separation(PDS)

    • ドメイン(Modelと呼ばれ、システム本来の関心領域)を、プレゼンテーション(Viewと呼ばれ、UIに関するロジック)から引き離す
    • PDSを実践する際の具体的なレイヤー構造をパターンとして示すのが、GUIアーキテクチャ

GUIアーキテクチャ

Model-View-Controller(MVC)モデル

UML

特徴

  • ウィジェット単位でプレゼンテーションロジックとドメインを分離する
  • Modelの変更にたいし、オブザーバー 同期が行われる

実装

課題

  • プレゼンテーションロジックの表現ができない
  • プレゼンテーション状態を表現できない
  • テストが難しい

Presentation Modelパターン(Application Modelパターン)

UML

特徴

  • Presentation Modelに、プレゼンテーションロジックやプレゼンテーション状態の管理を担わせる
  • MVCパターンの3つの課題を全て解決できる
    • ロジックがViewから切り離されていれば、Viewのインスタンスを用意しなくてもテスト可能
  • AspectAdaptorなどの仕組みの導入
    • SwiftでいうKeyPathによるKVOのようなもので、Modelが更新された時の動作をプロパティの指定だけで簡単に描けるようにサポートしてくれている(よくわからん)

課題

  • 特になし?

Model-View-ViewModel(MVVM)

UML

特徴

  • 構造は、Application Modelとほぼ変わらないが、Controllerのレイヤーが存在しない
  • ViewのテンプレートをXAML(XMLベースのDSL)で宣言する
  • その宣言に従い、システムが実行時に自動的にViewとViewModelをバインドするため、コードを書かずともViewModelの状態がViewへ反映される
  • Viewクラスは動的に
  • 関数型リアクティブプログラミング(Reactive Programming)と相性が良いので採用される
    • 手続き的にではなく、宣言的にロジックを表現できる

実装

  • Model
    • UIに関係しない純粋なドメインロジックやそのデータを持つ
    • ViewやViewModelがなくてもビルド可能
  • View
    • ユーザー操作の受け付けと画面表示を担当する
    • ViewModelが保持する状態とデータバインディングし、ユーザー入力に応じてViewModel自身が保持するデータを加工・更新する
  • ViewModel
    • View - Model間の画面表示のための仲介役
    • 責務は次の通り
      • Viewに表示するためのデータを保持
      • Viewからイベントを受け取り、Modelの処理を呼び出す
      • Viewからイベントを受け取り、加工して値を更新する
    • Modelとは違い、画面上でどのように表示されるかのために、必要な状態とロジックを担う
    • MVPのPresenterとの違い
      • Preseterと違い、Viewに対しての手続的な処理が不要なので、Viewの参照を保持する必要がない

課題(デメリット)

  • 大きなアプリケーションのためのアーキテクチャであり、単純なアプリケーションに置いては実装コストが「オーバーキル」(過剰)である
  • 大きなアプリケーションであったとしても、データバインディングがメモリ効率に影響を与える可能性がある
実装時に必要になった知識
  • NotificationCenterを使用して、MVVMアーキテクチャで実装する例はほとんどない
    • 使用する場合、これを使って送られたデータの型情報が失われてしまうのが、デメリット

参考

Model-View-Presenter(MVP)

  • 以下3つのMVPが存在する
    • MVP(Taligent)
    • MVP(Supervising Controller)
    • MVP(Passive View)

MVP(Taligent)

  • MVCのそれぞれの責務をさらに細かく分割し、Controllerを一般化した存在として、Presenterを置いた
  • Presenterは、アプリケーション全ての入力イベントを管理する

MVP(Supervising Controller)

  • Viewの特徴

    • MVCのViewとは異なり、ユーザー操作の受付をContollerではなく、Viewで行う
    • Viewが直接Modelの変更を監視する
      • オブザーバ同期
      • フロー同期
  • Presenterの特徴

    • Application Modelに似ているが、よりViewに近いレイヤー
      • Application Modelは、Modelに属する
    • Viewの実体を直接知っている
      • 複数のViewのインスタンスを管理し、それらを階層的に取り扱う
    • 1つのViewにつき、1つのPresenterを作成する(Passive Viewも共通)
      • 仲介役なので、ViewとModelの双方を知っているが、どちらを依存関係の主にするかは特に定められてない。
      • ただしiOSでは、ViewControllerが不可欠なので、「ViewがPresenterを知っている状態」とし、PresenterからはViewをweak参照で持つようにする
  • Modelの特徴

    • 純粋なドメインロジックの表現
  • MVP(Passive View)と異なる点

    • 複雑なプレゼンテーションロジックは、Presenterが担当し、簡単なものはViewに残す
    • ViewはControllerを介さずに直接Modelの更新を検知して、プレゼンテーションロジックを担当することもある

MVP(Passive View)

  • テストしやすい、TDDサイクルを回せるアーキテクチャ
  • Passive View
    • オブザーバ同期をやめたView
    • Viewが完全に受動的な存在に
  • Appleが提唱する「Cocoa MVC」はMVP(Passive View)のことと考えて良い
  • MVP(Supervising View)と異なる点
    • プレゼンテーションロジックを完全に、Presenterに担当させる
実装時に必要になった知識

Flux

UML

特徴

  • 単一方向のデータフロー
    • それによりどこで状態の変更が起きているか、わかりやすい
    • 状態に関するコードは Store が持つので、ViewController の肥大化を防ぐことができる
  • Action
    • ActionCreator
      • なんらかの処理を行い、Action を生成する
      • 生成した Action を Dispatcher に送信する
  • Dispatcher
    • ActionCreator から Action を受け取る
    • 受け取った Action を Store に伝える
    protocol Dispatcher {
      // ActionCreator が Action を送信する
      func dispatch(_ action: Action)
      // Store から呼び出して、Callback を登録し、Action を受け取る
      func register(callback: () -> Void)
    }
  • Store
    • Dispatcher の register関数を使って Callback を登録し、その Callback から Action を受け取る
    • 受け取った Action の type と data をもとに、自身の状態を更新する
    • 更新した場合は、View へ変更通知を送る
    • 任意のコンテキストに対して、1つの Store が存在
      • アプリケーションと同じライフサイクルか、シングルトンのクラスになりがち
    • Dispatcher に Action を送る前に Store を生成しないと、Action に対する処理が実行されない場合があるので、注意

Redux

特徴

  • Fluxを発展させ、関数型言語のElmの影響を大きく受けたアーキテクチャ

その他の整理

データの2つの同期方法

同期方法 概要 メリット デメリット 使用シーン
フロー同期 上位レイヤーのデータを下位レイヤーに都度セットしてデータを同期する、手続き的な同期方法。 ・データのフローを追いやすい
・手続き的なので、共通したデータを参照している全ての箇所の参照を持っておく必要がある。そのために参照の管理が煩雑になりやすい。 push遷移でドリルダウンしていくような隣り合った画面間でのデータ共有
オブザーバ同期 関し元である下位レイヤーが関し先である上位レイヤーからObserverパターンで送られるイベント通知を受け取ってデータを同期させる、宣言的な同期方法。 ・共通した複数箇所でデータを同期しやすい。 ・データが変更される度に同期処理が実行されるため、いつデータが同期されるかが追いづらくなる ・ハートや星マークでのお気に入りのデータ管理のように、複数タブや階層が離れた画面でも、各画面が共通のデータ領域の変更を関ししているために、同期箇所で他の画面の参照を持つ必要がない。
  • 注意したいこと
    • コンポーネント間の距離が遠いのに、無理にフロー同期を使用しないこと!
      • 上位レイヤーが知らなくても良い下位レイヤーを知る(依存する)ことになり、データフローを追いやすいというメリットがなくなるため
    • どちらが優れているとかではなく、それぞれメリットデメリットがあるので、設計段階でどちらを採用するか考える必要がある

「iOSアプリ設計パターン入門」を読んで考えたアーキテクチャ採用戦略

  • 以下については未反映(随時更新予定)
    • MVVM
    • Flux
    • Redux

MVPのSupervising ViewとPassive View

  • 選定基準
    • 「全てのプレゼンテーションロジックをテスト可能にしたいか?」
      • YES→Passive View
      • NO→Supervising View
    • アプリケーションで取り扱うプレゼンテーションロジックが簡単なものならば、テスト不要か?
      • YES→Supervising View
      • NO→Passive View
  • 迷った場合やMVPに慣れていない場合
    • Passive Viewを選択!
      • 「Viewが持つ簡単なプレゼンテーションロジック」の「簡単、」の判断が難しいため

システムアーキテクチャ

  • ドメインの先でどのようにレイヤーを切り分けるべきか、システム全体をどのように接合すべきかを示す仕組みのこと

レイヤードアーキテクチャ

  • 関心によってレイヤーを分ける
  • Domain層がData層に依存しないよう、Dependency Injection(依存オブジェクトの注入)を行った
    • それにより、Data層へのドメイン知識の漏洩は防がれる

Hexagonal Architecture(Ports And Adapters Architecture)

  • iOSにおけるプライマリアダプターとセカンダリアダプター

  • Presentation層でも、Domain層にあるべきドメイン知識が漏洩しがちだったが、それを防ぐ

    • DataやPresentation層を「外部との接続に関するレイヤー」として捉える
  • ポートとアダプターの概念がある

    • ポート
      • 外部との接続
      • 目的の単位で抽象化される
    • アダプター
      • 差し込まれた外部モジュールの実装詳細を隠蔽し、ポートが期待するインターフェースへの変換する
      • プライマリアダプター
        • アプリケーションを駆動するためのもの
      • セカンダリアダプター
        • アプリケーションによって駆動されるためのもの

参考

Onion Architecture

  • 基本的な考えは、Hexagonal Architectureと変わりない
  • 同じく、Data層を外側におき、Adapterパターンによってアプリケーションを疎結合に保つもの
  • Hexagonal Architectureとの違い
    • アプリケーションの視覚表現として、六角形ではなく円を用いている
    • アプリケーション内部を複数の円で分割している
  • 円の中心にあるのは、モデルの状態と振る舞いを表現するDomain Model
    • Domain ServiceやApplication Serviceがあるが、必ずしもそのレイヤーに分かれている必要はない
  • 4つの特徴
    • アプリケーションは自立したオブジェクトモデルを取り囲むように作られる
    • 内側のレイヤーはインターフェースを定義し、外側のレイヤーはそれを実装する
    • 依存の方向は外側から内側
    • Application Coreはインフラストラクチャ(Data層)抜きでコンパイル・実行できる

参考

Clean Architecture

  • 今まで提唱された様々なアーキテクチャを統合したもの

モバイルアプリにおけるアーキテクチャ

  • 画面遷移をどう設計するか

CoordinatorパターンとMVVM-C

  • アプリのルートに存在するApplicaion Coordinatorを頂点とした階層構造によって画面遷移を表現する
  • 一般的には、画面遷移を行う場合は、View Controller内で次のView Controllerをインスタンス化し、Navigation ControllerへのpushやModalのpresentを行うが、それだと、View Controllerは次のView Controllerを指定いる必要がある。
    • 遷移先が複数存在する場合や、使いまわしたい場合に、遷移ロジックが肥大化するのを防ぐ

参考

RouterパターンとVIPER

  • 「View」「Iteractor」「Presenter」「Entity」「Routing」
  • Clean Architecture + MVP(Passive View) + Router
    • RouterはPresenterに画面遷移を指示する
  • Routerの役割
    • 遷移先のView Controllerの生成
    • 遷移先のView Controllerが依存するコラボレーターの生成と代入
    • 画面遷移の実施方法の定義