Coordinated Surf MVP

Содержание

Предисловие

Этот архитектурный паттерн переродился из уже давно принятого за стандарт у нас в студии SurfMVP. Причиной появления новой надстройки над SurfMVP послужили проблемы с навгиацией в приложениях, которые начали разрастаться со временем, набирать массу сложных фич, реализация которых затруднялась при использование routers, которые мы ввели в SurfMVP.

Из-за того, что навигация в приложение написанном на SurfMVP строится посредством обращение одного модуля через router к другому модулю. Схематично это выглядит следующим образом:

SurfMVPNavigation

Схема навигации в Surf MVP

Coordinated Surf MVP меняет концепт навигации в приложение, убирает акцент с отдельно существующих модулей объединяя их в группы модулей, выполняющих общие действия.

Описание архитектуры

В основе архитектуры Coordinated Surf MVP лежит привычный для нас Surf MVP. Подробней можно прочитать тут.

SurfMVPNavigation

Схема Coordinated Surf MVP – модуль

Coordinated SurfMVP – это архитектурный паттерн, в котором, в отличие от SurfMVP, мы убрали сущность Router, которая находилась внутри каждого отдельного модуля. Парадигма построения приложения немного изменилась. Модули теперь не являются полностью независимыми. Каждый модуль, за исключением полностью переиспользуемых, находится в отдельном обособленном UserFlow, который по задумке должен выполнять какое-то общее действие, приводящее пользователя к желаемому результату.

Например, набор экранов авторизации может является примером такого флоу.

В Coordinated SurfMVP сущность Router заменила сущность Coordinator, которая теперь отвечает за работу навигации не одного отдельного модуля, а набора модулей, которые связаны друг с другом логически. Это упрощает навигацию и работу с приложением. Схематично приложение будет выглядить так:

SurfMVPNavigation

Схема приложения с Coordinated Surf MVP

В самом верху стоит ApplicationCoordinator, который отвечает за первоначальный роутинг в приложении. К примеру, кейс, когда пользователь авторизован, тогда мы его отправим сразу в основную часть приложения, в противном случае, мы отправим его на экран авторизации.

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

SurfMVPNavigation

Схема навигации в Coordinated Surf MVP

Плюсы и минусы Coordinated SurfMVP

Плюсы:

Переиспользование

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

Навигация

Так как логика навигации обособлена внутри отдельного координатора, теперь гораздо удобней следить за навигацией: достаточно открыть один файл и вся картина перед глазами. Нет больше необходимости протыкивать все отдельные модули, чтобы понять, что за чем тянется, собирать приложение и смотреть в дизайн.

Проектирование

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

Для этого стоит первым делом инициализировть все модули и описать их ModuleOutput и ModuleInput. После чего можно полностью реализовать Coordinator и связать все модули. Данную задачу может выполнять один человек. После данного этапа несколько разработчиков могут комфортно разрабатывать несколько модулей.

Интеграция Deeplinks и Push-Notifications перестала быть головной болью. Coordinated SurfMVP позволяют очень просто реализовать работу с любой внешней навигацией. Подробней про это можно прочитать в статье Панова.

Подробней про реализацию Для того чтобы держать всю информацию о навигации в одном месте создаем enum `DeepLinksOptions` в нем определяются все конечные модули, до куда мы хотим добраться. Далее необходимо реализовать методы по инициализации этого enum из мест где обрабатываются DeepLinks и/или Push-Notifications. После чего экземпляры данного enum необходимо пробросить по методам `start(with deepLinkOption: DeepLinkOption?)` до координатора, который сможет отобразить целевой экран. Таким образом выстраивается цепочка экранов, которые необходимо отобразить. В случае если DeepLink или Push-Notification приходит в момент, когда приложение активно, то в координаторах необходимых для построения стека приложения нам необходимо определить методы `handle(deepLinkOption: DeepLinkOption)`, которые позволят добраться до нужного координатора без пересоздания. Для определения, создан ли уже нужный дочерний координатор необходимо использовать generic-метод `hasDependency(ofType: T.Type)`, и уже основываясь на полученном зачение необходимо обрабатывать методы start или handle. </details> ### Минусы #### Большие координаторы Из-за концентрации всей логики в одном месте становится гораздо сложнее не утонуть в большом количестве строк кода. Если не следить за соблюдением принципа единой ответственности, то конечно, координатор может вырасти в большого монстра, и все плюсы по читаемости кода легко испарятся. #### Много кода Приходится много писать, чтобы достичь красоты в коде. Из-за большого количества слоев в приложении, каждый из которых отвечает за отдельное действие, приходится пробиваться через эти слои, чтобы дойти до желаемого координатора. #### Memory Leaks Проблема не нова, но стоит следить за этим делом, чтобы не попасть в просак. Основная причина появления утечек памяти при работе с координаторами – это retain-циклы в коллбеках модулей. Так что нужно очень внимательно следить за сильными ссылками внутри замыканий. Основной причиной появления утечек памяти при работе с координаторами является возможность использования **strong** ссылок внутри **closures**. Так как связь между координатором,
Типовой кейс Типовой кейс — инициализация нового Координатора и реализация closure finishFlow. Захват weak coordinator является обязательным, иначе Координатор будет ссылаться сам на себя, что повлечет утечку в виде AuthCoordinator. ```Swift func runAuthFlow() { let coordinator = AuthCoordinator(router: MainRouter()) coordinator.finishFlow = { [weak self, weak coordinator] in self?.removeDependency(coordinator) } self.addDependency(coordinator) coordinator.start() } ```
## UIAlertController and Coordinators При работе с Coordinator любая навигация должна выполняться в нем. `UIAlertController`также влияет на навигацию сам по себе, а зачастую еще и открывает экраны по выбору одного из action. Таким образом для работы `UIAlertController ` создается отдельный модуль SurfMVP, который отображается координатором. Для инициализации таких модулей был написа [шаблон](https://github.com/surfstudio/generamba-templates/tree/master/surf_mvp_coordinatable_alert). ## Когда использовать Coordinated SurfMVP - Структура экранов сложна и подвержена частым изменениям; - Есть Deeplinks и/или Push-Notifications со сложной навигацией; - У вас достаточно большая команда и приложение либо нужно писать с нуля, либо нужно реализовать большую фичу со связанными между собой модулями и их будут писать разные разработчики параллельно. ## Когда не стоит использовать Coordinated SurfMVP - Проект достаточно маленький и не планирует быстро развиваться; - На проекте очень простая структура экранов, и она не подвержена сильным изменениям. ## Кодогенерация Аналогично **SurfMVP** мы сделали [Generamba Template](https://github.com/surfstudio/generamba-templates). Подробней про это можно почитать [тут](/Surf-iOS-Developers/ru/Docs/tech-stack/architectures/Surf_MVP.html). Для интеграции в проект используйте заготовленные шаблоны для приложений с координаторами. Они находятся [тут](https://github.com/surfstudio/Xcode-Project-Templates).