Подтвердить что ты не робот

Вопросы о VIPER - Чистая архитектура

Я читал о чистой архитектуре от Роберта Мартина и более конкретно о VIPER.

Затем я столкнулся с этой статьей/сообщением Опыт бригад с использованием альтернативы MVC, который описывает в значительной степени то, что я сейчас делаю.

После того, как я попытался реализовать VIPER в новом проекте iOS, я столкнулся с некоторыми вопросами:

  • Достаточно ли, чтобы ведущий запрашивал информацию в представлении или должен ли "передача информации" всегда начинаться с представления? Например, если представление вызывало какое-либо действие в презентаторе, но затем, в зависимости от параметров, переданных этим действием, ведущему может потребоваться дополнительная информация. Я имею в виду: пользователь постучал "doneWithState:", если состояние == "что-то", получить информацию из представления, чтобы создать сущность, если состояние == "что-то еще", оживить что-то в представлении. Как я должен обрабатывать такой сценарий?
  • Допустим, что "модуль" (группа компонентов VIPER) решает представить другой модуль модально. Кто должен отвечать за принятие решения о том, будет ли представлен второй модуль модально, первый каркас модуля или каркас второго модуля?
  • Кроме того, скажем, что второй вид модуля вставляется в контроллер навигации, как следует обрабатывать действие "назад"? Должен ли я вручную установить кнопку "назад" с действием во втором контроллере представления модуля, который вызывает ведущего, который вызывает второй каркас модуля, который отклоняет и сообщает первому каркасу модуля, что он был уволен, так что первый контроллер представления модуля может хотите что-то отобразить?
  • Если разные модули говорят только через каркас или через делегатов между ведущими? Например, если приложение перешло к другому модулю, но после этого пользователь нажал "отменить" или "сохранить", и этот выбор должен вернуться и что-то изменить в первом модуле (возможно, отобразить анимацию, в которой она была сохранена или удалить что-то).
  • Предположим, что на карте был выбран вывод, чем отображается PinEditViewController. При возврате выбранный цвет контактов может потребоваться изменить в зависимости от действий использования на PinEditViewController. Кто должен сохранять состояние текущего выбранного булавки, MapViewController, MapPresenter или MapWireframe, чтобы я мог знать, когда вернемся, какой контакт должен изменить цвет?
4b9b3361

Ответ 1

1. Пусть докладчик запросит информацию из представления

Чтобы ответить на это, мы должны получить более подробную информацию о конкретном случае. Почему представление не может предоставлять больше контекстной информации непосредственно при обратном вызове?

Я предлагаю передать объект Presenter Command, чтобы Presenter не должен был знать, что делать в этом случае. Презентатор может выполнить объектный метод, передавая некоторую информацию самостоятельно, если это необходимо, не зная ничего о состоянии представления (и тем самым введя в него высокую связь).

  • Вид находится в состоянии, которое вы вызываете x (против y и z). Он все равно знает о своем состоянии.
  • Пользователь завершает действие. View сообщает своему делегату (Presenter) о завершении. Поскольку он задействован, он создает объект передачи данных, чтобы хранить всю обычную информацию. Одним из этих атрибутов DTO является id<FollowUpCommand> followUpCommand. View создает XFollowUpCommand (в отличие от YFollowUpCommand и ZFollowUpCommand) и соответственно устанавливает его параметры, а затем помещает его в DTO.
  • Ведущий получает вызов метода. Он делает что-то с данными независимо от того, какой конкретный FollowUpCommand существует. Затем он выполняет только протокол, followUpCommand.followUp. Конкретная реализация будет знать, что делать.

Если вам нужно выполнить оператор switch-case/if-else для некоторого свойства, большую часть времени он помог бы моделировать параметры как объекты, наследующие от общего протокола, и передать объекты вместо состояния.

2. Модальный модуль

Если модуль представления или представленный модуль решают, модально ли это? - Представленный модуль (второй) должен принять решение, если он предназначен только для использования в моделях. Поместите знания о вещи в самом деле. Если его режим представления зависит от контекста, ну, то сам модуль не может решить.

Второй каркас модуля получит следующее сообщение:

[secondWireframe presentYourStuffIn:self.viewController]

Параметр - это объект, для которого должна состояться презентация. Вы можете пройти вместе с параметром asModal, если модуль предназначен для использования в обоих направлениях. Если есть только один способ сделать это, поместите эту информацию в затронутый модуль (тот, который представлен) сам.

Затем он сделает что-то вроде:

- (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. Иерархия навигации

Кроме того, скажем, что второй вид модуля вставляется в контроллер навигации, как следует обрабатывать действие "назад"?

Если вы идете "все VIPER", да, вам нужно перейти от представления к его каркасу и перейти к другому каркасу.

Чтобы передать данные из представленного модуля ( "Второе" ) в модуль представления ( "Первый" ), добавьте SecondDelegate и реализуйте его в FirstPresenter. Перед появлением представленного модуля он отправляет сообщение SecondDelegate для уведомления об итогах.

"Не сражайтесь с каркасом". Возможно, вы можете использовать некоторые из тонкостей навигационного контроллера, пожертвовав чисткой VIPER. Segues уже шаг в направлении механизма маршрутизации. Посмотрите на 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 объект сообщение из Presenter A, что должно произойти тогда?

Так как представление получателя не отображается, анимация не может запускаться, например. Ведущему по-прежнему приходится ждать Wireframe/Router. Поэтому он должен вставить анимацию в очередь, пока она не станет активной снова. Это делает Presenter более состоятельным, что затрудняет работу с ним.

Архитектурно, подумайте о роли, которую играют модули. В архитектуре Ports/Adapters, из которой Clean Architecture норы некоторые концепции, проблема более очевидна. В качестве аналогии: компьютер имеет много портов. Порт USB не может связываться с портом LAN. Каждый поток информации должен быть проложен через ядро.

В чем заключается суть вашего приложения?

Есть ли у вас модель домена? У вас есть набор услуг, которые запрашиваются из разных модулей? Модули VIPER окружают вид. Компоненты модулей, как и механизмы доступа к данным, не принадлежат к определенному модулю. Это то, что вы можете назвать ядром. Там вы должны выполнить изменения данных. Если другой модуль становится видимым, он извлекает измененные данные.

Однако для простых целей анимации маршрутизатор знает, что делать и выдавать команду ведущему в зависимости от изменения модуля.

В коде VIPER Todo:

  • "Список" - это корневой режим.
  • В верхней части списка отображается представление "Добавить".
  • ListPresenter реализует AddModuleDelegate. Если модуль "Добавить" закончен, ListPresenter будет знать, а не его каркас, потому что представление уже находится в стеке навигации.

5. Состояние хранения

Кто должен сохранять состояние текущего выбранного вывода, MapViewController, MapPresenter или MapWireframe, чтобы я мог знать, когда вернется, какой контакт должен изменить цвет?

Отсутствует. Избегайте состояния в ваших модулях просмотра, чтобы снизить затраты на поддержание вашего кода. Вместо этого попытайтесь выяснить, можете ли вы передать представление изменений контактов во время изменений.

Попытайтесь достичь сущностей для получения состояния (через Presenter и Interactor и еще много чего).

Это не означает, что вы создаете объект Pin в своем уровне представления, передаете его из контроллера просмотра, чтобы просмотреть контроллер, изменить его свойства и затем отправить его обратно, чтобы отразить изменения. Будет ли NSDictionary с сериализованными изменениями? Вы можете поместить новый цвет туда и отправить его с PinEditViewController обратно в Presenter, который выдает изменение в MapViewController.

Теперь я обманул: MapViewController должно быть состояние. Он должен знать все контакты. Затем я предложил вам сменить словарь изменений, чтобы MapViewController знал, что делать.

Но как вы идентифицируете затронутый контакт?

Каждый вывод может иметь свой собственный идентификатор. Возможно, этот идентификатор - это просто его местоположение на карте. Может быть, это его индекс в массиве контактов. В любом случае вам нужен какой-то идентификатор. Или вы создаете идентифицируемый объект-обертку, который держится на самом выводе в течение всего периода действия. (Это звучит слишком смешно с целью изменения цвета).

Отправка событий в состояние изменения

VIPER очень услужлив. Существует множество объектов, не имеющих гражданства, связанных друг с другом для передачи сообщений и преобразования данных. В сообщении Brigade Engineering также показан подход, ориентированный на данные.

Сущности находятся в довольно тонком слое. Напротив спектра, который я имею в виду, лежит Модель домена. Этот шаблон не нужен для каждого приложения. Моделирование ядра вашего приложения аналогичным образом может быть полезным для ответа на некоторые из ваших вопросов.

В отличие от Entities в качестве контейнеров данных, в которые каждый может связаться с помощью "менеджеров данных", домен защищает его объекты. Домен также будет информировать об изменениях. (Через NSNotificationCenter, для стартеров. Меньше, так как прямые вызовы с помощью команды).

Теперь это может быть подходящим для вашего случая с PIN-кодом:

  • PinEditViewController изменяет цвет булавки. Это изменение в компоненте пользовательского интерфейса.
  • Изменение компонента пользовательского интерфейса соответствует изменению вашей базовой модели. Вы выполняете изменения через стек модуля VIPER. (Сохраняете ли вы цвета? Если нет, объект Pin всегда недолговечен, но он все еще является сущностью, потому что имеет значение его идентификация, а не только его значения.)
  • Соответствующий Pin изменил цвет и опубликовал уведомление через NSNotificationCenter.
  • Случайностью (то есть Pin не знает), некоторые Interactor подписываются на эти уведомления и меняют внешний вид своего вида.

Хотя это может сработать и для вашего дела, я думаю, что привязка редактирования

Ответ 2

Этот ответ может быть немного несвязан, но я помещаю его здесь для справки. Сайт Clean Swift - отличная реализация дяди Боба Чистая архитектура "быстро. Владелец называет его VIP (он все еще содержит" Entities" и Router/wireframe, хотя).

Сайт предоставляет вам шаблоны XCode. Итак, скажем, вы хотите создать новую сцену (он называет модули VIPER, "сцены" ). Все, что вы делаете, это File- > new- > sceneTemplate.

Этот шаблон создает партию из 7 файлов, содержащих всю головную болью шаблонного кода для вашего проекта. Он также настраивает их так, чтобы они работали из коробки. Сайт дает довольно подробное объяснение того, как каждая вещь подходит друг к другу.

Когда весь код плиты котла находится в стороне, поиск решений, которые вы задали выше, немного проще. Кроме того, шаблоны допускают согласованность по всей доске.

EDIT → Что касается комментариев ниже, объясните, почему я поддерживаю этот подход → http://stringerstheory.net/the-clean-er-architecture-for-ios-apps/

Также этот → Хороший, плохой и гадкий о VIPER в iOS

Ответ 3

На большинство вопросов вы ответите: https://www.ckl.io/blog/best-practices-viper-architecture (включая образец проекта). Я предлагаю вам обратить особое внимание на советы по инициализации/представлению модулей: до источника Router для этого.

Что касается кнопок "назад", вы можете use delegates включить это сообщение в желаемый модуль. Вот как я это делаю, и он отлично работает (даже после того, как вы вставляете push-уведомления).

И да, модули могут определенно разговаривать друг с другом using delegates. Это необходимо для более сложных проектов.