私は Clean Architecture について読んでいます。具体的には [〜#〜] viper [〜#〜] について読んでいます。
その後、この記事/投稿に出くわしました MVCの代替手段を使用した旅団の経験 これは、私が現在行っていることのほとんどを説明しています。
実際に新しいiOSプロジェクトにVIPERを実装しようとした後、いくつかの質問に遭遇しました。
満足のいく回答を得るには、特定のケースに関する詳細が必要です。コールバック時にビューがより多くのコンテキスト情報を直接提供できないのはなぜですか?
Presenterに Command オブジェクトを渡すことをお勧めします。これにより、Presenterはその場合に何をすべきかを知る必要がなくなります。プレゼンターは、オブジェクトのメソッドを実行し、ビューの状態について何も知らずに(必要に応じて高度な結合を導入することなく)必要に応じて独自の情報を渡します。
id<FollowUpCommand> followUpCommand
。ビューはXFollowUpCommand
(YFollowUpCommand
およびZFollowUpCommand
とは反対)を作成し、それに応じてパラメーターを設定し、DTOに配置します。FollowUpCommand
が何であろうと、データに対して何かをします。次に、プロトコルの唯一のメソッドfollowUpCommand.followUp
。具体的な実装は何をすべきかを知っています。何らかのプロパティでswitch-case/if-elseを実行する必要がある場合、ほとんどの場合、オプションを共通プロトコルから継承するオブジェクトとしてモデル化し、状態ではなくオブジェクトを渡すのが役立ちます。
提示モジュールまたは提示モジュールは、それがモーダルかどうかを判断する必要がありますか? -提示されたモジュール(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を使用する場合は、少し異なる方法で作業する必要があります。
また、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が表示されるかを表す唯一のスタックです。
異なるモジュールは、ワイヤフレームのみを使用して、またはプレゼンター間のデリゲートを介して通信する必要がありますか?
別のモジュールBのオブジェクトにプレゼンターAからメッセージを直接送信する場合、どうなりますか?
受信者のビューが表示されないため、たとえばアニメーションを開始できません。プレゼンターは、ワイヤフレーム/ルーターを待機する必要があります。そのため、アニメーションが再びアクティブになるまでキューに入れる必要があります。これにより、プレゼンターがよりステートフルになり、作業が難しくなります。
アーキテクチャに関しては、モジュールが果たす役割について考えてください。 Clean Architectureがいくつかの概念を掘り起こしているPorts/Adaptersアーキテクチャでは、問題はより明白です。たとえば、コンピューターには多くのポートがあります。 USBポートはLANポートと通信できません。情報のすべてのフローはコアを経由する必要があります。
アプリの中核は何ですか?
ドメインモデルはありますか?さまざまなモジュールから照会される一連のサービスがありますか? VIPERモジュールはビューの中心にあります。データアクセスメカニズムのように、共有するスタッフモジュールは、特定のモジュールに属していません。それがコアと呼ばれるものです。そこで、データの変更を行う必要があります。別のモジュールが表示されると、変更されたデータを取り込みます。
ただし、単なるアニメーションのために、ルーターに何をすべきかを知らせ、モジュールの変更に応じてプレゼンターにコマンドを発行します。
VIPER Todoサンプルコード:
戻るときに、どのピンの色を変更すべきかを知るために、現在選択されているピン、MapViewController、MapPresenter、またはMapWireframeの状態を保持する必要があるのは誰ですか?
なし。ビューモジュールサービスのステートフルネスを回避して、コードの保守コストを削減します。代わりに、変更中にピンの変更の表現を渡すことができるかどうかを把握してください。
エンティティが状態を取得できるようにします(プレゼンター、インタラクター、その他)。
これは、ビューレイヤーでPin
オブジェクトを作成し、View ControllerからView Controllerに渡し、そのプロパティを変更してから、変更を反映するために送信するという意味ではありません。シリアル化された変更を含むNSDictionary
は実行されますか?新しい色をそこに入れて、PinEditViewController
からMapViewController
の変更を発行するプレゼンターに送り返すことができます。
今、私はだまされました:MapViewController
は状態を持つ必要があります。すべてのピンを知る必要があります。次に、MapViewController
が何をすべきかを理解できるように、変更辞書を渡すことをお勧めします。
しかし、どのように影響を受けるピンを特定しますか?
すべてのピンには独自のIDがあります。たぶん、このIDは地図上のその場所にすぎません。多分それはピン配列のインデックスです。いずれにしても、何らかの種類の識別子が必要です。または、操作中にピン自体を保持する識別可能なラッパーオブジェクトを作成します。 (しかし、色を変えるという目的にはあまりにもばかげているように聞こえます。)
VIPERは非常にサービスベースです。メッセージを渡したり、データを変換したりするために、ほとんどステートレスなオブジェクトがたくさん結びついています。 Brigade Engineeringの投稿では、データ中心のアプローチも示されています。
エンティティはかなり薄い層にあります。私が念頭に置いているスペクトルとは反対に、 ドメインモデル があります。このパターンは、すべてのアプリに必要なわけではありません。ただし、アプリのコアを同様の方法でモデル化すると、いくつかの質問に答えることができます。
誰もが「データマネージャ」を介してアクセスできるデータコンテナとしてのエンティティとは対照的に、ドメインはそのエンティティを保護します。ドメインは、変更についても積極的に通知します。 (最初はNSNotificationCenter
を使用します。コマンドのようなダイレクトメッセージコールを使用する場合はそれほどではありません。)
これは、Pinケースにも適している場合があります。
Pin
エンティティは常に短命ですが、値だけでなくアイデンティティが重要なのでエンティティのままです。)Pin
は色が変わり、NSNotificationCenter
を介して通知を発行します。Pin
がわからない)、一部のInteractorはこれらの通知をサブスクライブし、ビューの外観を変更します。これもあなたの場合にはうまくいくかもしれませんが、私は編集を結ぶと思います
この答えは少し無関係かもしれませんが、参照のためにここに置いています。サイト 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
同様に。より複雑なプロジェクトには必須です。