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

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

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

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

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

    3 Solutions collect form web for “Вопросы о VIPER – Чистая архитектура”

    1. Может ли Presenter запрашивать информацию из представления

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

    Я предлагаю передать объект 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 в 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 в качестве контейнеров данных, в которые каждый может связаться через «менеджеров данных», Domain защищает свои сущности. Домен также будет информировать об изменениях. (Через NSNotificationCenter , для начинающих. Меньше, чем с помощью командных вызовов прямого вызова.)

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

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

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

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

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

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

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

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

    Также этот -> The Good, плохой и Ugly о VIPER в iOS

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

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

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

    Interesting Posts

    Отправка запроса POST с помощью NSURLConnection

    Как воспроизводить фоновое звуковое уведомление

    Центрирование столбца переменной ширины текста в UIScrollView с использованием автоматической компоновки

    iOS, почему система убивает приложение, используя местоположение в фоновом режиме

    моделирование обновления приложения в iOS

    Как выбрать заглавные буквы из строки Swift?

    Интеграция Dropbox с iOS

    iPhone – несколько целей множественные изображения запуска кошмара

    Неопределенные символы для архитектуры armv7 при использовании библиотеки ZXing в XCode 4.5

    Как использовать одно и то же уведомление для фона переднего плана в APNS

    ImageView в ListView вкратце показывает предыдущее изображение на прокрутке с включенным RecycleElement

    «Команда компоновщика завершилась с кодом выхода 1» в модульном тесте?

    Неявно Unwrapped Необязательные типы – возможно Typo?

    Изменить tintColor невыбранного заголовка элемента UITabBarController и фонового изображения

    Ошибка itms-90035

    PhoneC: Разработка iOS проста с помощью XCode, Swift3, UITableView, cocatouch, давайте создадим приложения для iPhone, iPad и Macbook.