40

Robert Martin のClean Architectureについて、具体的にはVIPERについて読んでいます。

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

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

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

3 に答える 3

19

1. プレゼンターはビューから情報を照会できます

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

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

  • ビューはxと呼ばれる状態にあります ( yおよびzとは対照的です)。とにかく、それは自分の状態を知っています。
  • ユーザーがアクションを終了します。View は、デリゲート (Presenter) に終了を通知します。非常に関与しているため、通常の情報をすべて保持するためにデータ転送オブジェクトを構築します。この DTO の属性の 1 つがid<FollowUpCommand> followUpCommand. View は ( and とは反対に) を作成し、XFollowUpCommandそれに応じてそのパラメーターを設定してから、それを DTO に入れます。YFollowUpCommandZFollowUpCommand
  • プレゼンターがメソッド呼び出しを受け取ります。具体的なものに関係なく、データに対して何かを行います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;
}

ストーリーボード セグエを使用する場合は、少し異なる操作を行う必要があります。

3. ナビゲーション階層

また、2 番目のモジュールのビューがナビゲーション コントローラーにプッシュされたとします。「戻る」アクションはどのように処理する必要がありますか?

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

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

ただし、「フレームワークと戦わないでください」。VIPER の純粋さを犠牲にすることで、ナビゲーション コントローラーの優れた点を活用できるかもしれません。セグエは、すでにルーティング メカニズムの方向への一歩です。カスタム アニメーションを導入するワイヤーフレームのメソッドについては、 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];
}

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

4. メッセージの流れ

異なるモジュールは、ワイヤーフレームのみを介して会話する必要がありますか、それともプレゼンター間のデリゲートを介して会話する必要がありますか?

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

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

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

アプリの核となるのは何ですか?

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

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

VIPER Todo サンプル コード:

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

5.状態保持

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

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

エンティティが状態を取得できるように (Presenter や Interactor などを介して) 手を伸ばしてみてください。

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

今私はだまされました:MapViewController状態が必要です。すべてのピンを認識する必要があります。次に、変更辞書を渡すことを提案したので、MapViewController何をすべきかがわかります。

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

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

状態を変更するためのイベントの送信

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

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

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

これもあなたのピンケースに適しているかもしれません:

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

これはあなたのケースでもうまくいくかもしれませんが、編集を結ぶと思います

于 2015-03-13T11:00:10.873 に答える
8

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

このサイトでは、XCode テンプレートを提供しています。では、新しいシーンを作成したいとしましょう (彼は VIPER モジュールを「シーン」と呼んでいます)。

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

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

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

また、これ -> iOS の VIPER についての良い点、悪い点、醜い点

于 2015-12-20T22:00:04.707 に答える
3

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

戻るボタンに関しては、use delegatesこのメッセージを目的のモジュールにトリガーできます。これが私のやり方であり、うまく機能します(プッシュ通知を挿入した後でも)。

そして、はい、モジュールは間違いなく互いに通信することもできusing delegatesます. より複雑なプロジェクトには必須です。

于 2017-04-11T01:54:22.347 に答える