Как стать автором
Обновить

Комментарии 6

Спасибо за статью!
Я не очень в теме паттерна координатора и не совсем понимаю плюсов от его использования.
Я вижу, что в статье упор сделан на навигацию с помощью UINavigationController. Но навигация бывает разной: есть UITabBarController, есть UISplitViewController, есть «простые» методы открытия новых экранов с помощью present, есть и кастомные реализации навигации, например, Drawer'ы, могут быть модули не владеющие UIViewController, например, ячейки коллекций и некоторые другие ситуации.
У меня нескольно вопросов:
1) Насколько гибкими могут быть координаторы и как сильно инкапсулировать в себе логику различных видов навигации и их сочетания?
2) Насколько удобно будет восстанавливать состояние навигации при перезапуске приложения?
3) Какие именно удобства предоставляет координатор? Почему логику навигации не оставить в Router (я его понимаю в контексте VIPER модуля)?
Спасибо за хорошие вопросы! Постараюсь прокомментировать максимально подробно и по порядку пока хотя бы все, что смогу.

«не совсем понимаю плюсов от его использования» / «Какие именно удобства предоставляет координатор?»
Лично мне координаторы приглянулись своей линейностью: один координатор – один шаг навигации. Шаг вперед – инициализация и start(), шаг назад – это, условно, onFlowFinished().

«Почему логику навигации не оставить в Router (я его понимаю в контексте VIPER модуля)?»
В данном приложении у нас до этого все было на роутерах, и все довольно быстро превратилось в клубок (понятно, что тут не столько роутеры виноваты, сколько люди их писавшие, но все же). Но VIPER-ом у нас не пахло: были MVC-/MVP- и MVVM-модули с навигацией по ним на роутерах.

«есть UITabBarController» / «Насколько удобно будет восстанавливать состояние навигации при перезапуске приложения?»
У нас основной режим приложения – это экран с таб-баром как раз. Мы написали MainCoordinator, который, условно, внутри start() определяет состояние приложения и запускает тот или иной координатор. Есть некий AuthorizedStateCoordinator, который порождается MainCoordinator и открывает таб-бар с контроллерами. С этого момента у нас пока действуют в основном старые принципы навигации, поэтому я не смогу поделиться опытом, можно ли и удобно ли использовать координаторы для навигации по вкладкам. Но многие составляющие модули, которые дописывались в последнее время, «запечатаны» внутри координаторов (например, у нас есть вкладка с меню – там сам бог велел).
Это, кстати, был один из пропагандируемых плюсов координаторов: начинать их использовать можно с любого места. Хотя бы чтобы просто понять, нравится или нет.

«Насколько гибкими могут быть координаторы и как сильно инкапсулировать в себе логику различных видов навигации и их сочетания? „
Все внутри и все hardcoded. Например, в start() может быть что-нибудь вроде navigationController.push(...), а в onFlowFinished() – очистка стека и передача управления другому координатору, который добавит свой контроллер в стек, и тот станет новым root. Грубо говоря, что угодно, но, к сожалению, только заранее определенно.
Сочетания навигаций, как я себе вижу, можно как-то осуществлять с помощью сочетаний координаторов. По изначальной идее, координатор инициализируется под определенный способ навигации (с экземпляром UINavigationController, как у меня тут, например). Но модули (UIViewController или, скажем, какие-то группы UITabBarController) могут и переиспользоваться в разных координаторах – они прямо точно не должны зависеть от навигационных принципов.

“могут быть модули не владеющие UIViewController, например, ячейки коллекций»
Это дело у нас все в «представлении» и управляется контроллером. Но у контроллеров есть некий навигационный делегат – то, что у меня названо Route – интерфейс, который реализуется порождающим координатором и получает обратные вызовы по событиям от контроллера (нажата кнопка, выбрана ячейка и т.п.) По выбору ячейки, например, может быть порожден другой, дочерний, координатор, в который будет передан принцип навигации (например, наш UINavigationController) и по start() произойдет push соответствующего контроллера.

В общем, мысли возникают какие-то такие. Но я буду стараться делиться и реальным опытом, когда что-то из этого буду пробовать на себе!
Мы поигрались с координаторами, но в итоге вернулись к подходу который использовали раньше в предыдущих проектах (swift и objc) и пока всем довольны. С координаторами начинается головная боль когда приходится решать вопросы диплинков и т.д. и задачи типа, а что если пользователь уже на этом экране, а что если пользователь в этот момент что то покупал, осталось только на кнопку нажать а тут он тапнул на пуш, и т.д. То есть поначалу казавшийся довольно изящным паттерн начинал обрастать странными иф/елсами, какие то вещи должны были быть весьма асинхронными и становились все более сложными в отладке. В итоге вернулись к старому подходу, и долизали его до библиотеки. Примерно описано тут:
habr.com/ru/post/421097
А делегаты живут абсолютно отдельно. Кому надо — подписываться и становится делегатом.
Большое спасибо, что поделились опытом – интересно!

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

Там есть несколько причин, сейчас уже все подробности не вспомню, попробую по пунктам:


  1. Классический пример координатора привносит знание во вью контроллер что будет происходить дальше (и у вас в начале статьи он есть). И это делает сложным A/B тестирование — например следующий контроллер будет показан пушем в текущий навигейшен контроллер или презентован модально. Та же проблема возникает с возвратом обратно


    func push(_ module: UIViewController, animated: Bool, completion: (() -> Void)?)
    func popModule(animated: Bool)    

    Конечно это решается более высоким уровнем абстракции как у вас или в других либах, но тем не менее.


  2. Различные асинхронные диплинки: Допустим у нас в URL в письме может быть продукт код, а показать контроллер мы можем только по продукт айди, то есть сперва надо сделать запрос на сервер, перевести продукт код в продукт айди и потом показывать уже соответствующий контроллер.


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


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


  5. Во время прохождения флоу на подключение к программе лояльности пользователь может забыть пароль от своего аккаунта, тогда ему придет письмо востановления пароля, где он тапнет в линк и внутри флоу будет еще один флоу восстановления пароля. После которого он может при желании продолжить флоу лояльности.


  6. Пользователь на онбоардинг скринах (тупо картинки), тапает на пуш, показывать продукт нужно другим совершенно способом.


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


  8. Все даже не упомню. Из серии анимация навигации в одно место еще не отиграла, а пользователь уже кликнул еще на какой то внешний енгейджмент.



Что было в голом остатке, сами координаторы являясь как бы архитектурным упрощающим паттерном требовали декомпозиция внутри, иначе они сами начинали выглядеть как спагетти. Туда начинала кроме того проваливаться бизнес логика, или они начинали обрастать координаторами координаторов. Координаторы сложно юнит тестить так как они завязаны жестко завязаны на UIKit.


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


Используется формализованная библиотека уже более года — полет нормальный. Но конфигурация требует изучения. Примерно как автолайаут команды. Но щас все девелоперы с ней знакомы и ни у кого не возникает проблем.


Каждый этап разделен на сущности, которые можно тестировать отдельно.


Задачи имеют доступ к некоторому объекту мы его зовем wireframe где простые методы — goToProductArray(productArrayId: String), goToProduct(product: String), goToAccount() еоторые просто содержат внутри развернутую конфигурацию, которую проигрывает роутер библиотеки. Что и как ни одна из задач не знает и все работает на ура.

Спасибо большое за развернутый ответ!
Пока что прочитал по-быстрому, некоторые пункты обязательно надо обдумать!
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.