strongself / The-Book-of-VIPER

the one and the only
Other
956 stars 130 forks source link

Вопросы по подходу Rambler к VIPER #12

Open delebedev opened 8 years ago

delebedev commented 8 years ago

Ребята, вы очень больше дело делаете, спасибо! У меня есть несколько вопросов, некоторые из которых я думаю будет полезно осветить в том или ином виде в книге про VIPER (если просто ответите в тикете тоже буду благодарен)

mogol commented 8 years ago

Привет, VIPER +Reactive, VIPER + Promises. Безусловно, что мысли о promises/reactive возникают за счет простой и явной структуры потока событий/данных в VIPER. Но на данный момент для нас совсем не понятно, зачем они нужны в VIPER. Использование синхронных методов там, где это возможно, позволяет и повысить читаемость, и упростить тестирование. Возможно, что на одном из проектов мы опробуем Promises/Reactive в качестве пилота. Тогда расскажем на Rambler.iOS обязательно.

etolstoy commented 8 years ago

@garnett Привет! Спасибо за отзыв и помощь в развитии :)

Касательно производительности Typhoon. У любого стороннего компонента всегда есть как свои плюсы, так и минусы. В случае Typhoon небольшие задержки на старте являются ценой за его использование. На самом деле, все не так критично, и в этом направлении постоянно ведется работа - @alexgarbarev, думаю, сможет вбросить информации на эту тему.

Вообще есть быстрый способ уменьшить это время. Если в проекте не используется автоинъекция, добавь в Info.plist ключ TyphoonAutoInjectionEnabled со значением в NO - все сразу станет намного лучше :)

Па второй части вопроса - Assembly в некоторых проектах бывает очень много. В моем текущем, к примеру, их порядка 40-50 штук, и количество еще будет расти.

alexgarbarev commented 8 years ago

@garnett Да, по поводу производительности Typhoon, интересно было бы посмотреть отчеты из профайлера, где, собственно, оно тормозит. Да, AutoInjection действительно замедляет производительность - оно сканирует все классы компонентом и ищет auto-injection свойства на старте приложения. Отключение этой функции может существенно сократить время старта. В остальном, мне кажется, проблемы с производительностью при старте быть не должно (А если есть - нужно посмотреть в чем и пофиксить)

mogol commented 8 years ago

@garnett Средний размер ваших Rambler приложений... LOC: 30k-60k Модулей: 10-40

andmedved commented 8 years ago

Добрый день, Очень понравился подход Rambler к VIPER. Буду крайне признателен, если вы поясните как у вас реализуется роутинг в приложениях на Swift. Насколько я понял, он будет отличаться от роутинга в приложениях на ObjC. В частности, интересно каким образом вы реализуете доступ роутера текущего модуля к ModuleInput следующего.

AndreyZarembo commented 8 years ago

Добрый день!

В Swift-версии, чтобы не использовать свизлинг, был создан базовый контроллер, который содержит в себе реализацию метода prepareForSegue. В ней у контроллера проверяется его соответствие специальному протоколу ViperModuleInputProvider. Этот протокол обязует класс иметь свойство moduleInput. Если протокол реализован, то moduleInput передается в блок конфигурации(который передается через segue.sender).

pomozoff commented 8 years ago

Здравствуйте.

Хотелось понять как в свифте без свизлинга происходит процесс перехода в другой модуль? Например, пользователь нажал на ячейку таблицы - надо нарисовать детальный контроллер по какому-то ID из ячейки. Вью-контроллер начал раскручивать сегвей, в какой момент управление попадает в роутер, если свизлинга нет? Получается или в сториборде нет сегвеев, или сторибордовые сегвеи надо душить в делегате перед рождением и отличать их от своих, чтобы не того не задушить.

letko-dmitry commented 8 years ago

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

CognitiveDisson commented 8 years ago

@letko-dmitry Привет. Отличный вопрос. Верно, эта логика находится в интеракторе, но не самого модуля "обучалки", модуля из которого он будет показываться. То есть получается следующий workflow: 1) По определенному событию, например событию о готовности view(viewDidAppear), presenter узнает, что пора показать "обучалку". 2) Presenter запрашивает у interactor'a, нужно ли ее показывать. 3) В результате ответа, interactor сообщает роутеру, что нужно открыть модуль. 4) Показывается модуль "обучалки" Такой подход мы используем во многих местах - например показ UIPopoverController. Для того что бы код не дублировался и легко переиспользовался, логику принятия решения можно вынести в хелпер interactor'a (solver), а логику показа в хелпер роутера(revealer).

ct4h commented 8 years ago

Всем привет. Хотелось узнать, как вы определяете какой экран нужно отображать при запуске приложения. Например, как пропускать экран авторизации для залогиненного пользователя?

AndreyZarembo commented 8 years ago

@сt4h Привет! Удобно в AppDelegate создвать стек экранов и отображать его. Вдвойне удобно, если распилить делегать по функциям с помощью https://github.com/rambler-ios/RamblerAppDelegateProxy.

Это позволяет правильно отобразить экраны при запуске для нового/старого/авторизованного пользователя, обработать диплинки и пуши.

ct4h commented 8 years ago

@AndreyZarembo спасибо за оперативный ответ.

iharkatkavets commented 8 years ago

Ребята привет. Интересуюсь VIPER. У меня есть вопрос о том как осуществлять переход на следующий модуль. Во первых Presenter держит weak ссылку на View. Получается больше никто не держит strong ссылку на View (до тех пор пока она не добавится в стек экранов). Но у вас сказано что переход на следующий модуль выполняет Router. Знает ли Router о View? Как он получает вью. Как создавать View с использованием Typhoon, ведь ее никто не держит (Presenter-weak property->View) и она убивается. Приходится выставлять scope = TyphoonScopeSingleton. Но если у нас много экранов - проблема памяти? Можете ли поподробнее об этом рассказать.

Во вторых. Куда обращается Presenter текущего модуля если нужно перейти на другой экран? Правильно ли я понял что он держит ссылку на Router следующего модуля и вызывает его метод? Так ли что весь Assembly слой модуля скрыт в Typhoon и в модуле нету ссылок на Assembly.

Спасибо!

CognitiveDisson commented 8 years ago

@igorkotkovets Привет. Да, все верно, весь модуль держится в памяти за счет view. На view только две слабых ссылки - одна у Presenter и одна у Router текущего модуля. Переход на следующий экран осуществляет Router текущего модуля (view закрыта протоколом для перехода ), когда об этом просит Presenter. При этом Router создает viewController следующего модуля и сразу добавляет в стек, но не сохраняет на него ссылку. Стек навигации держит контроллер и весь модуль. Когда создается контроллер, Typhoon создаст и остальный части модуля. В основном мы используем следующие способы создания контроллера:

 [TyphoonDefinition withFactory:[self.storyboardAssembly someStoryboard]
                                  selector:@selector(instantiateViewControllerWithIdentifier:)

Для удобства мы используем ViperMcFlurry.

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

Presenter не держит ссылку на Router другого модуля, только на свой.

Более подробно можно посмотреть в RamblerConferences.

phil-alekhin commented 7 years ago

Здравствуйте! Большое вам спасибо за то что вы делаете!

У меня есть вопрос по поводу передачи данных в другой модуль на Swift. Поле moduleInput у протокола ModuleInputProvider это класс реализующий протокол ModuleInput? И не совсем понимаю что такое блок конфигурации. Заранее спасибо!

aknew commented 7 years ago

Добрый день. Возник такой вопрос: обратил внимание что в вашем приложении https://github.com/rambler-digital-solutions/rambler-it-ios ссылка на другие сториборды часто (может и всегда, все сториборды не проверял) идет через идентификатор контроллера, а не через установку у одного из контроллеров своства  is initial view controller, даже если других точек входа в сториборду нет (например, в Report.storyboard только два вьюконтроллера и только у одного из них есть идентификатор). Хотелось бы уточнить - дело только в личных предпочтениях или же есть какая-то иная причина, которую я не понял?

maxsava commented 7 years ago

@aknew Как я понял, так просто удобнее. В твоем примере ссылка на этот контроллер идет из Main.storyboard. Гораздо легче из роутера просто дернуть segue и uikit сделает все за тебя (инициализирует контроллер из нужного сториборда), чем самому указывать сториборд и вызывать у наго instantiateInitialViewController. Т.е. из кода нам даже не нужно знать, что нужный нам контроллер находиться в другом сториборде, мы просто дергаем segue. P.S. я не из Rambler'a. Возможно, у ребят был какой-то другой мотив.

aknew commented 7 years ago

Ты не понял, вопрос не про seque как подход - с ним все понятно. Вопрос в том что ссылку на контроллер в другой сториборде можно установить двумя способами:

  1. указать в сториборде один из контроллеров как точку входа и тогда он будет цепляться автоматом при создании ссылки на эту сториборду в другой
  2. указать у контроллера в ториборде идентификатор и прописать его в ссылке на сториборду вручную (т.к. автоподстановки нет), а это потенциальный источник ошибок т.к. вроде бы на этапе сборки оно не проверяется Ребята из Рамблера почему-то используют второй способ, хотя и с первым никаких проблем вроде как не возникает (я пробовал), при этом на первый взгляд в большинстве случаев иметь несколько точек входа в сториборду им не надо. Вот я и интересуюсь - не упустил ли я чего, может, есть какая-то причина или подход который я не понял?
etolstoy commented 7 years ago

@aknew Спасибо за вопрос! Как раз таки в основном из-за того, что может быть несколько точек входа в сториборду и лучше в явном виде указывать, на какой экран мы хотим перейти.

aknew commented 7 years ago

Спасибо за ответ

iharkatkavets commented 7 years ago

Здравствуйте,

Меня интересует вопрос по архитектуре. Приведу пример, допустим у меня в приложении есть какой-то сервис закачек файлов, который может работать в фоне. Есть какой-то модуль, который использует данные и отображает количество загруженых файлов и тп. Так вот, какой компонент модуля (презентер или интерактор) держит ссылку на сервис и получает данные от него? Мне кажется, что если добавить ссылку на сервис в интерактор - то это добавляет избыточности, порождает дополнительные пробросы Service->Interactor->Presenter. Но если я правильно понимаю, презентер не должен держать ссылку на сервис.

Как вы поступаете в этом случае?

DevAlloy commented 7 years ago

@igorkotkovets Приветствую! Стандартное решение - интерактор, именно он ответственен за взаимодействие с сервисным слоем, соответственно он и держит ссылку на сервис.

В случае, если разрешить подключение сервиса в Presenter в данном случае, то может возникнуть желание делать это и в дальнейшем. Происходит смешение ответственностей P и I, чего допускать не стоит.

iharkatkavets commented 7 years ago

@DevAlloy спасибо за ответ!

aknew commented 7 years ago

Добрый день. Есть несколько вопроса:

  1. Переход между модулями в swift - как он осуществляется? Я так понял, используются extension для UIViewController, но при этом шаблон swifty_viper не добавляет во вью такой протокол, а роутер и вовсе вещь в себе без связи с чем-либо кроме протокола RouterInput т.е. перейти из него куда-либо не получится. Что это, недоработка шаблона или я что-то не догоняю (что легко может быть особенно в свифте)?
  2. Составные модули - как осуществляется роутинг в них, через moduleOutput? Допустим, у меня есть задача реализовать ячейку информации о докладе с переходом по клику на полный текст доклада и переходом по клику на какую-то кнопку на ячейке в редактирование информации о докладе, при этом ячейка будет видна из списка докладов конференции и из списка докладов в профиле автора
  3. Немного не по теме - вы советуете использовать Typhoon, как вы решаете проблему с анализом проекта (пункт меню product->analyze он же clang-analyze)? По моим наблюдениям, т.к. Typhoon устанавливает связи в рантайме анализатор оказывается неспособным их проследить, вырожденный пример с делением на нуль могу накидать. С уважением
iSevenDays commented 7 years ago

@aknew 1. https://github.com/strongself/The-Book-of-VIPER/issues/21

aknew commented 7 years ago

Спасибо, но это не совсем то - по ссылке описано как использовать objc-протокол из ViperMcFlurry (я кстати так уже сделал к моменту написания вопроса), но вот тут https://www.youtube.com/watch?v=m4MYKzlqtH8 утверждалось вроде что можно сделать через extantions. Впрочем, вопрос еще и в том что ни один из этих способов, по-видимому, не заложен в темплейт swifty_viper

svvoff commented 7 years ago

@aknew В видео говорилось про расширения протоколов. Тут есть реализация переходов между модулями с помощью transition handler-а, но пока без передачи данных.

ManWithBear commented 7 years ago

День добрый. Как правильно реализовать левое меню через VIPER? В голову приходят варианты: 1) SideMenuModule (SMM), MenuModule (MM), ContentModule (CM) SMM отвечает за саму логику выдвижной панели, получает необходимый экран от Module Output у MM и в Router'e меняет CM 2) SideMenuModule (SMM), ContentModule (CM) Здесь SMM выполняет роль выдвижной панели + само меню, соответственно Presenter получает уведомления от View, и передает их Router'у, который меняет CM 3) Что-то лучше?

DevAlloy commented 7 years ago

Первый вариант самый предпочтительный на мой вгляд

ManWithBear commented 7 years ago

@DevAlloy благодарю.

EvgenyIv96 commented 7 years ago

Здравствуйте, как автоматизировать весь процесс в app extension (виджет), там нет app delegate(как добавить rambler assembly collector?), модуль из сториборда автоматически не собирается...

ghost commented 7 years ago

Здравствуйте. Если возможно очень хотелось бы услышать несколько доводов в пользу использования Storyboard-ов в контексте VIPER или отказа от них. Многие отказываются совсем от Storyboard-ов, приводя как аргумент, что их неудобно мерджить и удобнее в одном месте контроллировать изменения внутренностей вью контроллера. Есть ли примеры практик имплементации VIPER архитектуры в приложении без применения Storyboard? Может быть у Вас был опыт и Вы отказались в пользу использования Storyboard-ов по каким то причинам?

Спасибо за ответ

serkrapiv commented 7 years ago

@dst-bengus мы с самого начала использовали VIPER со сторибордами на всех проектах, поскольку "проблемы" при работе с ними нам кажутся надуманными. Если это не проект размером с Facebook, конечно. Поэтому опыта работа без сторибордов особо нет.

ghost commented 7 years ago

@serkrapiv Спасибо за ответ. Тогда вопрос сформулирую по другому, хотя оффтоп получается. Какие откровенные преимущества Вашей команде дает использование Storyboard, кроме НЕпринесения "проблем"? Интересно услышать мнение команды, работающей в количестве >3 над проектами не из одного экрана.

AndreyZarembo commented 7 years ago

@dst-bengus Привет! Я тут со своим самоваром влезу ) Если говорить про преимущества, то в Storyboard удобно смотреть из чего собирается экран, когда он модульный. Особенно если активно используется IBDesignable. Ну и переходы на другие экраны так смотреть удобно, если их не очень много. Также стоит активно использовать референсы и бить сториборды по Userstory. Условно процесс покупки в одном месте, поиск в другом и т.п. Особенно при работе в команде. Тогда будет меньше конфликтов, борды будут быстрее открываться и не будет паутины из переходов.

И ещё, если приложение активно поддерживает диплинки, которые могут открывать очень глубокие части приложения, формировать историю просмотра, либо поддерживает State Restoration, переходы между экранами и наполнение их модулями лучше делать без Segue. Это позволит создавать экраны синхронно, с большой вложенностью и простой настройкой. Так удобнее и быстрее. При таком подходе от Storyboard остается по сути вместилище разметки для контроллеров. Если она простая, то действительно стоит совсем от Storyboard отказаться.

ghost commented 7 years ago

@AndreyZarembo Спасибо. Как раз примеры переходов между модулями без Segue и разыскиваю вменяемые. Потому, что испытываю личную неприязнь к Storyboard)

AndreyZarembo commented 7 years ago

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

ghost commented 7 years ago

@AndreyZarembo Спасибо еще раз

ManWithBear commented 7 years ago

@dst-bengus я как раз из тех сторонников не использования сторибордов. Так что все сториборды заменены на xib'ы. Главный плюс: можно использовать кастомные иниты, в которые можно прокидывать зависимости.

ghost commented 7 years ago

@ManWithBear свой человек! Но я иду дальше и интерфейс билдер вообще не использую. гибче получается. Даешь весь UI из кода.

nemissm commented 6 years ago

Вопрос по работе с moduleOutput. Кто держит этот объект? И кто вызывает его методы? Первоначально он приходит в презентер, раз в RamblerViperModuleInput есть - (void)setModuleOutput:(id<RamblerViperModuleOutput>)moduleOutput; Смотрим дальше. По-сути, это протокол делегата модуля. В этом случае первым параметром должен идти вызывающий объект, как например у - (void)scrollViewDidScroll:(UIScrollView *)scrollView;. Т.е в нашем случае передаваться должен viewController? Если держать moduleOutput в презентере и из него вызывать методы moduleOutput, то мы не сможем передать его, т.к view закрыт протоколом. Тут в примере методов: https://etolstoy.gitbooks.io/the-book-of-viper/content/%D0%B2%D0%BE%D0%BF%D1%80%D0%BE%D1%81%D1%8B-code-style.html вообще опущен этот параметр.

serkrapiv commented 6 years ago

@nemissm в наших проектах moduleOutput держался презентером дочернего модуля. Этот самый презентер и вызывал методы moduleOutput при необходимости.

Что касается передачи вызывающего объекта первым параметром - да, по канонам здесь первым параметром надо передавать презентер дочернего модуля (в виде id). Но по факту это почти никогда не нужно, поэтому мы обычно не передаём.

sdanny commented 5 years ago

Привет. Прошу разрешить давний спор касательно view-слоя. В книге сказано

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

Предположим, модуль представляет из себя таблицу с однотипными данными. Правильно ли я понимаю под кэшированием хранение пассивных моделей во view для установки значений аутлетов этих ячеек в cellForRowAtIndexPath-методе?

Brain89 commented 5 years ago

@sdanny да

superkramar1990 commented 5 years ago

@Brain89 ребята, раскройте пожалуйста вопрос более широко,

"Не храните во view controller данные. View controller выступает посредником между модельным слоем и слоем представления при обмене данными. Он может кешировать некоторые данные для быстрого доступа, валидировать их, но его основная обязанность - гарантировать, что view отображает правильную информацию."

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