Подход к созданию модулей через фабрику, описываемый в статьях про канонический VIPER, достаточно неудобен. Мы решили попробовать родные UIStoryboardSegue
для переходов между модулями. Такой подход открывал заманчивые перспективы - ведь для перехода в другой модуль необходимо было бы всего лишь указать SegueID и передать в модуль данные для работы. Кроме того, для конфигурации модулей мы используем Typhoon, поэтому все модульные ViewController после инициализации через Segue уже имеют связи с другими компонентами модуля.
Это самый простой вариант. У роутера вызывающего модуля есть ссылка на свой ViewController, при переходе на другой модуль у ViewController вызывается метод -prepareForSegue:
, где в sender
передаются данные для следующего модуля. Внутри -prepareForSegue:
вызывающего ViewController эти данные передаются в следующий модуль.
Такой подход работает, но есть и некоторые недостатки:
- Логика настройки следующего модуля размещается внутри View, а не в Router,
- Нет универсальности и переиспользования, этот метод нужно реализовывать в каждом модуле,
- Данные для работы следующего модуля попадают во View, а не в Presenter,
- Каждый модуль знает об устройстве другого модуля,
- Каждый роутер знает, что работает с классом
UIViewController
, и схема работает только для этого варианта.
Для решения первых двух проблем были использованы method-swizzling и блоки. В -prepareForSegue:
в sender
отправляется блок, в котором выполяется настройка модуля через destinationViewController
. В альтернативном методе -prepareForSegue:
блок вызывается с destinationViewController из segue в качестве параметра.
Это работает, логика настройки следующего модуля находится целиком внутри Router, для каждого модуля больше не требуется добавлять во ViewController метод -prepareForSegue:
, но остаются три проблемы:
- Данные для работы следующего модуля попадают во View, а не в Presenter,
- Каждый модуль знает об устройстве другого модуля,
- Каждый роутер знает, что работает с ViewController и схема работает только для этого.
Чтобы решить оставшиеся проблемы были использованы протоколы. Много протоколов. А также swizzling и своя реализация promise. В итоге получилась система передачи данных между модулями без перечисленных недостатков, данные из презентера отдаются роутеру и он конфигурирует ими презентер следующего модуля. Но появились две новые проблемы:
- На освоение у нового разработчика уходило порядка 2х дней,
- Данные передавались только в одну сторону.
Текущий вариант, доступный в нашем Github под названием ViperMcFlurry стал гораздо проще в освоении. У каждого модуля теперь есть точка входа - ModuleInput, которая позволяет настроить модуль или вызывать методы. Этот moduleInput можно использовать внутри роутера для настройки модуля, можно вернуть презентеру, для постоянной связи с подмодулем. У каждого модуля можно задать ModuleOutput, чтобы вернуть данные из модуля. ModuleInput/Output - это протоколы, которые задаются внутри модуля, то есть в них хранится контракт связи с ним. В большинстве модулей в роли ModuleInput выступает презентер этого модуля, а в качестве ModuleOutput - презентер вызывающего модуля.
Поскольку для метода -performSegue
требуется только имя перехода, -prepareForSegue:
подвергся swizzle'ингу, а Typhoon настраивает модуль по ViewController, то мы можем использовать любые классы Segue и механизм переходов между модулями будет работать.
Поэтому для встраивания модулей был создан специальный тип UIStoryboardSegue
EmbedSegue. Внутри -performSegue
у SourceViewController вызывается метод, который возвращает View для идентификатора Segue. В эту View и встраивается модуль.