-
Notifications
You must be signed in to change notification settings - Fork 128
Переходы между модулями
Теперь я расскажу про переходы между модулями и передачу данных между ними. Подход к созданию модулей через фабрику, описанный в статье про VIPER на Objc.io неудобен тем, что каждому модулю нужно было задавать ссылку на фабрики следующих модулей, а также добавить то, что будет выполнять переход между модулями. Т.е. практически заново создать Segue.
Мы решили попробовать родные StoryboardSegue для переходов между модулями. Открывались заманчивые перспективы: Для перехода в другой модуль необходимо было бы указать SegueID для самого перехода и передать в модуль данные для работы. Для конфигурации модулей мы используем Typhoon, поэтому все модульные ViewController после инициализации через Segue уже имеют связи с другими компонентами модуля.
Это самый первый и простой вариант. У роутера вызывающего модуля есть ссылка на свой ViewController, при переходе на другой модуль у ViewController вызывается метод PrepareForSegue, где в Sender передаются данные для следующего модуля. Внутри prepareForSegue вызывающего ViewController эти данные передаются во ViewController следуюшего модуля.
Это работает, но есть некоторые недостатки:
- Логика настройки следующего модуля размещается внутри View, а не в Router
- Нет универсальности и переиспользования, prepareForSegue надо делать в каждом модуле.
- Данные для работы следующего модуля попадают во View, а не в Presenter
- Каждый модуль знает об устройстве другого модуля
- Каждый роутер знает, что работает с ViewController и схема работает только для этого.
Для решения первых двух проблем были использованы... свизлинг и блоки! В PrepareForSegue в Sender отправляется блок, в котором выполяется настройка модуля через DestinationViewController. В засвизлено методе PrepareForSegue проверяется, если это блок, то он вызывается с destinationViewController из segue в качестве параметра.
Это работает, логика настройки следующего модуля находится целиком внутри Router, больше не надо для каждого модуля добавлять во ViewController метод prepareForSegue, но остаются три остальных проблемы
- Данные для работы следующего модуля попадают во View, а не в Presenter
- Каждый модуль знает об устройстве другого модуля
- Каждый роутер знает, что работает с ViewController и схема работает только для этого.
Чтобы решить оставшиеся проблемы были использованы протоколы. Много протоколов. А также, свизлинг и мини-вариант промисов. В итоге получилась система передачи данных между модулями без тех недостатков, данные из презентера отдаются роутеру и он конфигурирует ими презентер следующего модуля. Но появились два новых недостатка:
- На освоение у нового разработчика уходило порядка 2х дней
- Данные передавались в одну сторону
Текущий вариант, доступный в нашем Github под названием ViperMcFlurry стал гораздо проще в освоении. У каждого модуля теперь есть точка входа - ModuleInput, которая позволяет настроить модуль или вызывать методы. Этот moduleInput можно использовать внутри роутера для настройки модуля, можно вернуть презетеру, для постоянной связи с подмодулем. У каждого модуля можно задать ModuleOutput, чтобы вернуть данные из модуля. ModuleInput/Output - это протоколы, которые задаются внутри модуля, т.е. в нем хранится контракт связи с ним. В большинстве модулей в роли ModuleInput выступает презентер этого модуля, а в качестве ModuleOutput - презентер вызывающего модуля.
Поскольку PreformSegue требуется только имя перехода, а PrepareForSegue засвизлен, а Typhoon настраивает модуль по ViewController, мы можем использовать любые классы Segue и механизм переходов между модулями будет работать.
Поэтому для встраивания модулей бы создан EmbedSegue. Внутри perform у SourceViewController вызывается метод, который возвращает View для идентификатора Segue. В этот View и встраивается модуль.
Но у Segue есть проблема, до iOS 9 они работали только внутри одной Storyboard, а нам нужно поддерживать iOS 7/8. При этом на проектах работает несколько разработчиков и нужно разделять Storyboard, чтобы уменьшить количество конфликтов при слиянии ветов. Так был создан PlaceholderViewController и специальная категория для Segue, которые позволяют сделать переходы между контроллерами в разных Storyboard. У Segue, конечно же, засвизлен метод destinationViewController, который проверяет класс целевого контроллера. Если это PlaceholderViewController, у него запрашивается целевой viewController. Placeholder создает целевой контроллер по своему RestorationID, имеющему формат Имя_сториборды@имя_контроллера. Создает Storyboard, из неё инициализирует контроллер и возвращает в Segue.
Это позволяет использовать все классы Segue, в том числе стандартные и Embed, при этом разбить Storyboard на несколько и использовать передачу данных между модулями.