web-dev-qa-db-ja.com

VIPER-Clean Architectureに関する質問

私は Clean Architecture について読んでいます。具体的には [〜#〜] viper [〜#〜] について読んでいます。

その後、この記事/投稿に出くわしました MVCの代替手段を使用した旅団の経験 これは、私が現在行っていることのほとんどを説明しています。

実際に新しいiOSプロジェクトにVIPERを実装しようとした後、いくつかの質問に遭遇しました。

  • プレゼンターがビュー内の情報を照会しても問題ありませんか、または「情報の受け渡し」は常にビューから開始する必要がありますか?たとえば、ビューがプレゼンターで何らかのアクションをトリガーしたが、そのアクションを介して渡されたパラメーターに応じて、プレゼンターがさらに情報を必要とする場合があります。つまり、ユーザーは「doneWithState:」をタップします。state==「something」の場合、ビューから情報を取得してエンティティを作成し、state ==「something」の場合、ビュー内の何かをアニメーション化します。この種のシナリオをどのように処理すればよいですか?
  • 「モジュール」(VIPERコンポーネントのグループ)が別のモジュールをモーダルに提示することを決定したとしましょう。 2番目のモジュールをモーダルで表示するかどうか、最初のモジュールのワイヤフレームまたは2番目のモジュールのワイヤフレームのどちらを決定する責任は誰にありますか?
  • また、2番目のモジュールのビューがNavigation Controllerにプッシュされたとしましょう。「戻る」アクションをどのように処理する必要がありますか? 「戻る」ボタンを手動で2番目のモジュールのView Controllerで設定し、プレゼンターを呼び出し、2番目のモジュールのワイヤフレームを呼び出して最初のモジュールのワイヤフレームに通知します。何かを表示したいですか?
  • 異なるモジュールは、ワイヤフレームのみを使用して、またはプレゼンター間のデリゲートを介して通信する必要がありますか?たとえば、アプリが別のモジュールに移動した後、ユーザーが「キャンセル」または「保存」を押した場合、その選択を戻して最初のモジュールで何かを変更する必要があります)。
  • PinEditViewControllerが表示されるよりも、ピンがマップ上で選択されたとしましょう。戻るとき、選択されたピンの色は、PinEditViewControllerの使用アクションに応じて変更する必要がある場合があります。戻るときに、どのピンの色を変更すべきかを知るために、現在選択されているピン、MapViewController、MapPresenter、またはMapWireframeの状態を保持する必要があるのは誰ですか?
37
Rodrigo Ruiz

1.プレゼンターがビューから情報を照会することがあります

満足のいく回答を得るには、特定のケースに関する詳細が必要です。コールバック時にビューがより多くのコンテキスト情報を直接提供できないのはなぜですか?

Presenterに Command オブジェクトを渡すことをお勧めします。これにより、Presenterはその場合に何をすべきかを知る必要がなくなります。プレゼンターは、オブジェクトのメソッドを実行し、ビューの状態について何も知らずに(必要に応じて高度な結合を導入することなく)必要に応じて独自の情報を渡します。

  • ビューは、xyおよびzとは反対)と呼ばれる状態です。とにかくその状態について知っています。
  • ユーザーはアクションを終了します。 Viewは、終了したことをデリゲート(プレゼンター)に通知します。非常に複雑なので、通常のすべての情報を保持するデータ転送オブジェクトを構築します。このDTOの属性の1つはid<FollowUpCommand> followUpCommand。ビューはXFollowUpCommandYFollowUpCommandおよびZFollowUpCommandとは反対)を作成し、それに応じてパラメーターを設定し、DTOに配置します。
  • プレゼンターはメソッド呼び出しを受け取ります。具体的なFollowUpCommandが何であろうと、データに対して何かをします。次に、プロトコルの唯一のメソッドfollowUpCommand.followUp。具体的な実装は何をすべきかを知っています。

何らかのプロパティでswitch-case/if-elseを実行する必要がある場合、ほとんどの場合、オプションを共通プロトコルから継承するオブジェクトとしてモデル化し、状態ではなくオブジェクトを渡すのが役立ちます。

2.モーダルモジュール

提示モジュールまたは提示モジュールは、それがモーダルかどうかを判断する必要がありますか? -提示されたモジュール(2番目のモジュール)は、モーダルのみで使用するように設計されている限り、を決定する必要があります。 モノ自体にモノに関する知識を入れます。プレゼンテーションモードがコンテキストに依存している場合、モジュール自体は決定できません。

2番目のモジュールのワイヤフレームは、次のようなメッセージを受け取ります。

[secondWireframe presentYourStuffIn:self.viewController]

パラメータは、プレゼンテーションが行われるオブジェクトです。モジュールが両方の方法で使用されるように設計されている場合、asModalパラメーターも渡すことができます。その方法が1つしかない場合は、この情報を影響を受けるモジュール(提示されたモジュール)自体に入れてください。

それは次のようなことをします:

- (void)presentYourStuffIn:(UIViewController)viewController {
    // set up module2ViewController

    [self.presenter configureUserInterfaceForPresentation:module2ViewController];

    // Assuming the modal transition is set up in your Storyboard
    [viewController presentViewController:module2ViewController animated:YES completion:nil];

    self.presentingViewController = viewController;
}

Storyboard Seguesを使用する場合は、少し異なる方法で作業する必要があります。

3.ナビゲーション階層

また、2番目のモジュールのビューがNavigation Controllerにプッシュされたとしましょう。「戻る」アクションをどのように処理する必要がありますか?

「すべてVIPER」に移行する場合は、はい、ビューからワイヤフレームに移動して、別のワイヤフレームにルーティングする必要があります。

提示されたモジュール( "Second")から提示モジュール( "First")にデータを戻すには、SecondDelegateを追加してFirstPresenterに実装します。提示されたモジュールがポップする前に、結果を通知するためにSecondDelegateにメッセージを送信します。

「フレームワークと戦わないで」。 VIPERの純粋さを犠牲にすることで、Navigation Controllerの優れた機能を活用できるかもしれません。セグエは、すでにルーティングメカニズムの方向への一歩です。 VTDAddWireframeを見てください カスタムアニメーションを導入するワイヤフレームのUIViewControllerTransitioningDelegateメソッドの場合。たぶんこれは助けになります:

- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
    return [[VTDAddDismissalTransition alloc] init];
}


- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented
                                                                  presentingController:(UIViewController *)presenting
                                                                      sourceController:(UIViewController *)source
{
    return [[VTDAddPresentationTransition alloc] init];
}

最初に、ナビゲーションスタックと同様のワイヤフレームのスタックを保持する必要があり、すべての「アクティブな」モジュールのワイヤフレームは互いにリンクされると考えました。しかし、そうではありません。ワイヤフレームはモジュールのコンテンツを管理しますが、ナビゲーションスタックは、どのView Controllerが表示されるかを表す唯一のスタックです。

4.メッセージフロー

異なるモジュールは、ワイヤフレームのみを使用して、またはプレゼンター間のデリゲートを介して通信する必要がありますか?

別のモジュールBのオブジェクトにプレゼンターAからメッセージを直接送信する場合、どうなりますか?

受信者のビューが表示されないため、たとえばアニメーションを開始できません。プレゼンターは、ワイヤフレーム/ルーターを待機する必要があります。そのため、アニメーションが再びアクティブになるまでキューに入れる必要があります。これにより、プレゼンターがよりステートフルになり、作業が難しくなります。

アーキテクチャに関しては、モジュールが果たす役割について考えてください。 Clean Architectureがいくつかの概念を掘り起こしているPorts/Adaptersアーキテクチャでは、問題はより明白です。たとえば、コンピューターには多くのポートがあります。 USBポートはLANポートと通信できません。情報のすべてのフローはコアを経由する必要があります。

アプリの中核は何ですか?

ドメインモデルはありますか?さまざまなモジュールから照会される一連のサービスがありますか? VIPERモジュールはビューの中心にあります。データアクセスメカニズムのように、共有するスタッフモジュールは、特定のモジュールに属していません。それがコアと呼ばれるものです。そこで、データの変更を行う必要があります。別のモジュールが表示されると、変更されたデータを取り込みます。

ただし、単なるアニメーションのために、ルーターに何をすべきかを知らせ、モジュールの変更に応じてプレゼンターにコマンドを発行します。

VIPER Todoサンプルコード:

  • 「リスト」はルートビューです。
  • リストビューの上部に「追加」ビューが表示されます。
  • ListPresenterはAddModuleDelegateを実装します。 「追加」モジュールが完了すると、ビューはすでにナビゲーションスタックにあるため、ListPresenterはそのワイヤフレームではなく、認識します

5.状態を維持する

戻るときに、どのピンの色を変更すべきかを知るために、現在選択されているピン、MapViewController、MapPresenter、またはMapWireframeの状態を保持する必要があるのは誰ですか?

なし。ビューモジュールサービスのステートフルネスを回避して、コードの保守コストを削減します。代わりに、変更中にピンの変更の表現を渡すことができるかどうかを把握してください。

エンティティが状態を取得できるようにします(プレゼンター、インタラクター、その他)。

これは、ビューレイヤーでPinオブジェクトを作成し、View ControllerからView Controllerに渡し、そのプロパティを変更してから、変更を反映するために送信するという意味ではありません。シリアル化された変更を含むNSDictionaryは実行されますか?新しい色をそこに入れて、PinEditViewControllerからMapViewControllerの変更を発行するプレゼンターに送り返すことができます。

今、私はだまされました:MapViewControllerは状態を持つ必要があります。すべてのピンを知る必要があります。次に、MapViewControllerが何をすべきかを理解できるように、変更辞書を渡すことをお勧めします。

しかし、どのように影響を受けるピンを特定しますか?

すべてのピンには独自のIDがあります。たぶん、このIDは地図上のその場所にすぎません。多分それはピン配列のインデックスです。いずれにしても、何らかの種類の識別子が必要です。または、操作中にピン自体を保持する識別可能なラッパーオブジェクトを作成します。 (しかし、色を変えるという目的にはあまりにもばかげているように聞こえます。)

イベントを送信して状態を変更する

VIPERは非常にサービスベースです。メッセージを渡したり、データを変換したりするために、ほとんどステートレスなオブジェクトがたくさん結びついています。 Brigade Engineeringの投稿では、データ中心のアプローチも示されています。

エンティティはかなり薄い層にあります。私が念頭に置いているスペクトルとは反対に、 ドメインモデル があります。このパターンは、すべてのアプリに必要なわけではありません。ただし、アプリのコアを同様の方法でモデル化すると、いくつかの質問に答えることができます。

誰もが「データマネージャ」を介してアクセスできるデータコンテナとしてのエンティティとは対照的に、ドメインはそのエンティティを保護します。ドメインは、変更についても積極的に通知します。 (最初はNSNotificationCenterを使用します。コマンドのようなダイレクトメッセージコールを使用する場合はそれほどではありません。)

これは、Pinケースにも適している場合があります。

  • PinEditViewControllerはピンの色を変更します。これは、UIコンポーネントの変更です。
  • UIコンポーネントの変更は、基礎となるモデルの変更に対応しています。 VIPERモジュールスタックを介して変更を実行します。 (色を保持しますか?そうでない場合、Pinエンティティは常に短命ですが、値だけでなくアイデンティティが重要なのでエンティティのままです。)
  • 対応するPinは色が変わり、NSNotificationCenterを介して通知を発行します。
  • 偶然(つまり、Pinがわからない)、一部のInteractorはこれらの通知をサブスクライブし、ビューの外観を変更します。

これもあなたの場合にはうまくいくかもしれませんが、私は編集を結ぶと思います

16
ctietze

この答えは少し無関係かもしれませんが、参照のためにここに置いています。サイト Clean Swift は、SwiftのUncle Bobの " Clean Architecture "の優れた実装です。所有者は、これをVIPと呼びます(ただし、「エンティティ」とルーター/ワイヤフレームはまだ含まれています)。

このサイトでは、XCodeテンプレートを提供しています。したがって、新しいシーンを作成したいとします(彼はVIPERモジュール、「シーン」と呼びます)、あなたがすることはFile-> new-> sceneTemplateだけです。

このテンプレートは、プロジェクトの定型コードの頭痛の種をすべて含む7つのファイルのバッチを作成します。また、すぐに使えるように設定します。このサイトでは、あらゆるものがどのように組み合わされるかについて、かなり徹底した説明を提供しています。

すべてのボイラープレートコードが邪魔にならないため、上記の質問に対する解決策を見つけるのが少し簡単になります。また、テンプレートにより、全体にわたって一貫性が保たれます。

[〜#〜] edit [〜#〜]->以下のコメントに関して、このアプローチをサポートする理由について説明します-> http://stringerstheory.net/the-clean-er-architecture-for-ios-apps/

これも-> iOSでのVIPERの良い点、悪い点、Uい点

あなたの質問のほとんどは、この投稿で回答されています: https://www.ckl.io/blog/best-practices-viper-architecture (サンプルプロジェクトが含まれています)。モジュールの初期化/プレゼンテーションのヒントに特に注意することをお勧めします。それを行うのはソースRouter次第です。

戻るボタンについては、use delegatesこのメッセージを目的のモジュールに送信します。これは私がそれを行う方法であり、(プッシュ通知を挿入した後でも)うまく機能します。

そして、はい、モジュールはusing delegates 同様に。より複雑なプロジェクトには必須です。

2