16 марта в 13:01

Через MVP к VIPER. Часть первая: MVP


Не так давно я публиковал перевод Почему VIPER это плохой выбор для вашего следующего приложения. И, как это часто бывает, мнение переводчика не совпало с мнением автора оригинальной статьи. Поэтому в этом посте я коротко расскажу как я пытаюсь (пока еще пытаюсь) внедрить VIPER в свои проекты.


Когда я начал работу над своим предыдущим проектом, в команде было ровно два целых ноль десятых мобильных разработчика: один писал версию под Андроид, второй – под iOS.


Естественно, iOS версия создавалась на классическом, рекомендуемом самим Apple, паттерне MVC.


У меня была View: "любимый" сториборд, в котором было over9000 довольно много экранов, и который был похож на это:



У меня была модель – классы, для хранения настроек, данных пользователя, некоторых данных, которые сохранялись во внутренней БД.


И у меня был вью-контроллер для каждого экрана на сториборде. Во вью-контроллере должны были размещаться обработчики действий пользователя, а также уведомления от модели (получение данных по запросу или изменения данных). Но на самом же деле там происходило нечто вот такое:


Networking.request(.POST, withURL: serverAddress, andParameters: parameters) { (dictResult, error) in
    if let dictResult = dictResult {

        // some actions with view

        if let rates = dictResult["rates"] as? [AnyObject] {
            for rate in rates {
                if let rateDictionary = rate as? JSONDictonary {
                    let result = Result(jsonDictionary: rateDictionary)
                    self.resultsArray.append(result)
                }
            }
        }

        // some actions with view

    } else {
        if let error = error {
            DispatchQueue.main.async(execute: {
                self.showError(error)
            })
        }
    }
}

В общем: я уверенно двигался от Model View Controller к Massive View Controller и с этим нужно было что-то делать.


Я начал изучать возможные архитектуры для мобильных приложений, и конечно, мне сразу же понравился паттерн Clean architecture (он же, по сути – VIPER):



картинка из The Book of VIPER


Я смело потратил несколько выходных на изучение этого паттерна. Просмотрел несколько видео-туториалов и написал немало простых приложений-примеров на нем. Также нашел удобные скрипты для генерации фалов модулей, ведь один модуль это – около десятка файлов (протоколы, конфигураторы, непосредственно классы, сториборды, юнит-тесты). Но довольно быстро пришло понимание, что если я сейчас попробую внедрить VIPER “в лоб”, то потеряю массу времени. Ведь с непривычки, у меня будут слишком большие временные затраты на правильное разделение ответственности между всеми слоями. И я решил пойти путем попроще и разделять ответственность постепенно.



А что если я скажу тебе, что UIViewController — это View.

Именно поэтому я взялся за MVP. Самое сложное и в то же время – самое простое – понять, что UIViewController – относится к слою View. Он должен работать с UI и только с ним: принимать от пользователя действия связанные с UI (нажатия кнопок, ввод текста, etc.) и менять отображение самого UI. Сначала это казалось сложным, но я вывел для себя несколько простых правил, которые помогают положить во вью-контроллер все необходимое, и при этом не положить туда ничего лишнего:


  1. Все методы и алгоритмы вью-контроллера должны работать с UI. Если вы видите какой-то метод или кусок кода, который может работать без использования UIKit, знайте: этому методу/блоку кода здесь не место.


  2. Для презентера, все в точности наоборот: там не должно быть ни одного метода для выполнения которого нужен UIKit. Если такие методы обнаружены – нужно внимательно изучить эти участки кода и вынести их во вью-контроллер.


  3. У вью-контроллера и презентера есть протоколы на которые они подписаны, это их внешние интерфейсы. Там определены методы, которые можно вызывать для них извне. Поэтому, если вдруг, вы видите во вью-контроллере вызов метода из интерфейса (протокола) этого же вью-контроллера – этот метод должен находиться не во вью-контроллере, а в презентере.

Переход на MVP, научил меня главной вещи: разделять ответственность между сущностями. Пока что, я только отделил работу с UI от всего остального, но впереди, меня все-таки ждет VIPER. Возможно классический, а возможно и более многослойный.


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

Serge Sukhanov @s_suhanov
карма
16,0
рейтинг 21,7
 iOS developer
Самое читаемое Разработка

Комментарии (12)

  • +1
    Еще бы с примерами… ммм…
  • +1
    Эм, а в чем суть статьи? Статей про MVP с примерами, как именно лучше все разделять и прочее тут тьма. Хотя, надо заметить, я не видел таких статей под iOS, но под android их реально много, поищи их, почитай.
  • +1
    > паттерн Clean architecture

    Да не паттерн это, а подход к архитектуре с разделением кода на слои. У меня стойкое чувство, что последователи VIPER не утруждают себя просмотром лекции дяди Боба на эту тему (статья менее выразительна, на мой взгляд и хуже описывает основной посыл).
  • +2
    Увы — VIPER не панацея, а скорее убийство в лоб, слишком радикально. Все таки VIPER хорош в теории, но путь к улучшению у вас близок к моему.
    Мы в компании сейчас тестируем свой упрощеный «VIPER», хоть теперь это и не VIPER вовсе, но все принципы остались. В стандартной моедли (и в модели от Rambler) слишком много запутанностей, и теория разрушается той практикой, что написана в приложении-примере.
    Но подход у нас примерно тот же —
    1. VC это все таки view
    2. Presenter — business layer, state machine
    3. Interactor — core
    4. Services & managers — разнорабочие
    + additionals: collection mediator — помогает вынести логику для коллекций, в которой приходится смешивать UI&business
    • +1
      «Стандартная модель» VIPER — это статейка от Mutual Mobile с не очень хорошим примером, который хипстеры начали пытаться копировать, как священное писание. Рамблер попытался таки натянуть эту сову на глобус реальности, но это все тот же шаблонный подход (в буквальном смысле ибо есть генерируемые шаблоны классов).

      Проблемы VIPER:
      1. во всех реализациях, что я видел, на каждый экран создается один presenter и один interactor. В случае достаточно сложного экрана получаем massive presenter и massive interactor (от чего вроде бы пытались уйти). Хотя Дядя Боб и Mutual Mobile говорили об interactor'е на отдельный юз кейс. Только недавно Рамблер начал понимать, что c этим нужно что-то делать.
      2. отсутствие состояния и единого источника истины (текущего состояния того что мы видим на экране). Дядя Боб описывал Clean Architecture на примере веб приложения типа тонкий клиент, там это нормально, но iOS приложения — толстые клиенты, поэтому нельзя слепо копировать этот подход.
      3. нечеткая ответственность presenter'а и interactor'а. Часто presenter просто пробрасывает вызовы между view и interactor не имея логики, что делает его избыточным.
      4. смешанная ответственность UIViewController, который помимо управления жизненным циклом своего view берет на себя обязанности рендеринга.


      И это только вершина айсберга под названием  VIPER.
  • +3
    В общем: я уверенно двигался от Model View Controller к Massive View Controller и с этим нужно было что-то делать.


    Да, научиться писать код. Никто в здравом уме не парсит JSON в контроллере
  • 0
    Использую Clean Swift Architecture в нескольких проектах, результаты радуют. Единственное, что напрягает — это невозможность использовать какой-нибудь функционал (цикл View -> Interactor -> Presenter) в нескольких сценах. Как в VIPER с этим справляются? Часто же бывает, что два вью контроллера делают схожие вещи.
    • 0
      Выносить общую логику отдельных view в «подмодули», созданные специально для view. Solved!
      • 0
        И как в этом случае обеспечить цикл VIP?
        • 0
          Мы в таких случаях начали плодить базовые протоколы и классы и уже от них наследоваться. А что-то уходило и в категории.
          Хотя бывало что переиспользовали и отдельные части, скажем, когда все в целом то же самое, вид выглядит по другому для какого-то одного частного случая.
        • 0
          как обычно. Не вижу тут проблем — общая логика view в подмолулях, логика зависящая от parent-моделя в нем. Плюс module input/output, Плюс сервисы со своими зонами ответственности — в общем проблем нет никаких
  • 0
    У меня была View: «любимый» сториборд, в котором было over9000 довольно много экранов, и который был похож на это


    Это проблема не MVC или Storyboard как таковых, это проблема композиции и организации.
    В нормальном проекте это было бы 5-8 разных сторибоардов и половина ViewController, которые вы видите должны быть разбиты на контейнеры. Тогда и проблема Massive VC уходит.
    Да и вообще в более 50% случаев проблема MassiveVC — это проблема людей, которые пишут код, а не архитектурного подхода как такового.

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