web-dev-qa-db-ja.com

コンポーネント間のブラインドメッセージを許可する委任パターンの代替

オブザーバーのタイプに関係なく、特定のメッセージをオブザーバーに渡すことを可能にするデリゲートとデリゲートが存在します。コーディネーターがアプリケーションのフローを管理および実行するコーディネータータイプのアーキテクチャー(たとえば、iOSおよびUIKitの代わりにUIレイヤー)では、委任は子コーディネーターとその親の間の主要な通信手段として機能します。問題は、これらのデリゲートコールは一般に、問題のアプリケーションに非常に固有であることです。

典型的な例は次のようになります。

protocol AuthenticationCoordinatorDelegate {
    func coordinatorDidPressLoginButton()
    func coordinatorDidPresentAuthenticationPage()
}

このプロトコルは認証コーディネーターにのみ適用され、その所有者によってのみ実装されます。これは1つの場所で使用される非常に特殊なユースケースであり、他のすべてのコーディネーターが通信する方法を必要とするため、ボイラープレートが横行しています。アプリケーションが大きくなるほど、より多くのコーディネーターが存在し、より汎用的な委任インターフェースを作成する必要があります。

コンポーネント間のブラインドメッセージングの受け渡しを可能にする委任パターンの可能な代替手段は何ですか?私が思っていたのは、何かが発生したことをデリゲートに通知する代わりに、アクションが中央ストアにディスパッチされ、親コーディネーターが適切に反応するReduxスタイルのアーキテクチャだと思っていました。もちろん、そのパターンには、状態ツリーのボタンを押すなどのアクションをモデル化する方法や、その状態ツリーを永続化する方法(具体的にはモバイル)など、厳しい制限があります。

誰か提案やアイデアがありますか?

3
barndog

コンポーネント間の匿名リンクを提供する簡単な方法の1つは、「通知」にクロージャプロパティを使用することです。

たとえば、nameプロパティを持つオブジェクトには、次のものも含まれます。

_var didChangeName: ((old: String, new: String) -> Void)?
_

nameが変更されると、self.didChangeName?(old: oldName, new: newName)が呼び出されます。

その所有者、またはMVVM設定のビューなどの変更に関心のある別のオブジェクトは、このプロパティを以前に設定して、適切なアクションを実行します。

_viewModel.didChangeName = { [weak self] (old, new) in
    self?.reactToNameChange(old, new)
}
_

これは、a)any他のオブジェクトが通知で好きなことを何でもできるという点で非常に柔軟です。 b)そのオブジェクトは、気にしない通知を処理する必要はありません*。およびc)おそらく必要になることはまれですが、個別のオブジェクトが通知セットのさまざまな部分に自由に登録できます。これは、従来のデリゲートセットアップでは不可能です。

一方、各クロージャに「サブスクライブ」できるのは1つのオブジェクトだけなので、KVO/Rxのようなソリューションよりも柔軟性が少し低くなります。また、制御チェーンを介して通知を渡すことは完全に明示的であり、各レベルでコールバックを手動でリンクする必要があります。例えば:

_// Parent object
viewModel.modelDidChange = { [weak self] in
    self?.handleModelChange()
}

// Child object
subViewModel.didChangeName = { [weak self] (old, new) in
    // Do things...
    self?.modelDidChange()
}
_

2番目の利点は、クロージャを設定したこと以外にクライアントの要件がないため、非常にテストしやすいことです。

_func testNotifiesViewOnNameChange()
{
    let newName = Model.dummy.name
    var viewModel = ViewModel(model: Model.dummy)
    var sentDidUpdate = false
    viewModel.didUpdate = { sentDidUpdate = true }

    viewModel.name = newName

    XCTAssertTrue(sentDidUpdate)
}
_

これをさらに詳しく知りたい場合は、 非反応性MVVMに関するIan Keenの記事 および GitHubの付随プロジェクト を参照してください。それは私がこのテクニックについて学んだところで、私はそれが素晴らしい説明だと思います。


*厳密にSwiftのプロトコルでは不可能

2
jscs

さて、頭に浮かぶ単純な「パターン」はNSNotificationCenterを使用しています。これは、直接接続されていないView Controllerを更新する必要がある場合や、複数のオブザーバーを一度に更新する必要がある場合に便利です。保持サイクルを防ぐために、オブザーバーを必ず削除してください。

1
Eneko Alonso