UIKit + Viper или MVC здорового человека

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

    Введение


    Начитавшись туториолов о Viper я начал пробовать создать на нем новый проект. В итоге мой Viper выглядел примерно так:

    image

    Рядом с каждым блоком подписан базовый класс. Стрелками обозначены ссылки между объектами (пунктиром — weak).

    Данная схема не решает крайне важную проблему. Каким образом располагать модули в UINavigationController? Из-за того, что стек навигации необходимо добавлять наследников класса UIViewController.

    • придется в router протягивать view через presenter и явно указывать, что это UIViewController
    • после добавления view в UINavagationController, у view счетчик сильных связей увеличится до 2-х. В последствии придется вручную убирать из памяти viper модуль.

    В таком подходе очевидна некоторая несовместимость Viper и UIKit.

    Как подружить Viper и UIKit


    В первую очередь необходимо понять, как перейти от Apple MVC к Viper. MVC достаточно легко раскладывается на view, presenter и interactor. Но у viper появляется еще дополнительный слой — router. И на этом моменте я предлагаю альтернативную точку зрения(во всяком случае на просторах интернета я ничего подобного не видел).

    image

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

    В ней сказано:
    Presenter/Презентатор

    Presenter — это PONSO, который в основном состоит из логики, чтобы управлять UI. Он собирает входные данные от взаимодействия с пользователем, таким образом, он может отправлять запросы Interactor'у. Presenter также получает результаты Interactor'а и преобразовывать результаты в состояние, которое является наиболее эффективным для отображения на View.

    Entity никогда не передаются из Interactor'а к Presenter'у. Вместо этого простые структуры данных, у которых нет поведения, передаются из Interactor'а к Presenter'у. Это препятствует любой ‘реальной работе’ в Presenter'е. Presenter может только подготовить данные для отображения на View.

    Если в качестве presenter взять не PONSO, а UIViewController, что изменится? Да presenter начнет отвечать за отображение модуля, но только под капотом UIKit. Все методы жизненного цикла, можно расценивать по такому же принципу, как события от кнопок view. То есть view сообщает presenter, что случился viewdidload и с этим можно что-то сделать. Вся логика отрисовки осталась на view.

    Теперь давайте рассмотрим router(в статье изображен, как wireframe. Я обычно разделяю на router и moduleManager)

    Wireframe/Каркас

    Маршрутизация обрабатывает навигацию от одного экрана к другому, как определено в wireframes, созданных проектировщиком взаимодействия. Wireframe объект несет ответственность за маршрутизацию. Wireframe объект владеет объектами UIWindow, UINavigationController, и т.д. Он ответственен за создание Interactor, Presenter и View/ViewController и за настройки ViewController. Так как Presenter содержит логику, чтобы реагировать на ввод данных пользователем, Presenter знает, когда перейти на другой экран. Wireframe знает, как это сделать. Итак, Presenter — это пользователь Wireframa.

    Встает вопрос. Почему router не может сам являться UINavigationController? Зачем нам еще велосипед, когда в UIKit есть свои роутеры — это UINavigationController и сочувствующие. В подобных классах уже заложена основная логика.

    Что решает такой подход?

    • меньше кода, все сделано за нас
    • понятный код, для человека, незнакомого с Viper
    • за работу с памятью теперь отвечает UIKit, руками не придется уничтожать модули
    • простая интеграция со сторонними библиотеками, которые на входе принимают UIViewController

    В качестве примера я создал небольшой демо проект. Надеюсь на объективную дискуссию.
    Поделиться публикацией
    Ммм, длинные выходные!
    Самое время просмотреть заказы на Фрилансим.
    Мне повезёт!
    Реклама
    Комментарии 9
    • +1

      У вас вышела некая школьная/упрощенная версия Вайпера. В целом архитектура не плохая и ее можно использовать при этом не прикладывая много усилий в борьбе с uikit. Но это не вайпер, так как основаная идея вайпера это соблюдение принципов solid. А в вашей версии они все нарушены, слишком многого ответственностей на каждом из компонентов. Увеличена связность, тестировать сложнее. Но думаю для мелких проектов и проектов где на первом месте скорость разработки, оно зайдёт.

      • 0
        Спасибо за комментарий! Если вам не сложно, опишите пожалуйста подробно, где я что нарушил? Все-таки эта статья была больше написана для того, чтобы разобраться самому почему так делать не стоит и как правильно
        • 0
          Еле коротко то вы все нарушили. Начнем с роутера, это компонент отвечающий за переход от одного модуля к другому, UINavigationController отвечает за все что угодного кроме этого, фактически это контейнер для контроллеров, он не знает куда надо переходить дальше. UIViewController опять же не разделим с View он управляет анимацией, навигацией и даже просто внешним видом. Он фактически весь состоит из того чего в нем быть не должно. С interactor`ом и view у вас особых проблем нет, так как они остались из изначального вайпера. Почитайте про solid, поймете что ваша архитектура не про него. Попробуйте покрыть модуль тестами и снова поймете что у вас проблемы. Ваша архитектура это скорее MVC от эапла как она должна быть + interactor. Это лучше чем просто massive view controller, но не более.
          • 0
            Было бы прекрасно, если бы вы дали пример, где можно увидеть более правильное построение вайпера. Так-как я видел только достаточно костыльные реализации
            • 0
              Можно в шаблонах для generamba посмотреть
              например
              https://github.com/rambler-digital-solutions/generamba-catalog/tree/master/swifty_viper
              • 0
                Спасибо! В данном примере получилось тоже самое, только вид с боку. По сути весь модуль держит вью, что на мой взгляд не есть хорошо. Так-же презентер просто ждет сигнала от вью в методе viewIsReady, в своей статье я сделал на этом акцент, что все то-же самое можно сделать, если прентер и будет viewcontroller'ом. Но вот никак не раскрыто, что делать с navigationController и ему подобными.
                • 0
                  Я вам объяснил что это не то же самое, в презентере нет ничего лишнего, он ничего не знает о view, его легко тестировать, его легко понять, у него всего одна ответственность. То что модуль в памяти держит View это издержки архитектуру apple, и пусть это не лучший вариант, он равно намного лучше чем делать презенте из VC.
      • +1
        «MVC здорового человека» — звучит вызывающе. А я бы назвал статью «Как придумать себе проблемы». Если лыжи не едут, то логично их смазывать, а не приделывать к ним педали.
        • 0
          Один из бенефитов вайпера, это упрощение тестирования компонентов за счет соблюдения solid ( как уже было скзаано выше). Как вы предполагаете тестировать ваши компоненты, если бОльшая их часть завязана на UIKit?

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.