Comments 47
По-вашему, лучше так все и оставить? Или хотя бы пытаться перевести это на нормальную архитектуру, с разделением слоев? Я сам писал долгое время приложения, как попало. И они работают и приносят мне небольшой доход. Но сейчас понимаю, что их тяжело поддерживать из-за того, что криво написаны.
Между VIPER и отсутствием архитектуры есть тот же MVC, или MVP, или MVVM. И на некоторых проектах они могут быть куда удобнее VIPER.
Почему Вы решили, что VIPER это не MVC? Визуальное представление как было, так и осталось. Контроллер как был, так и остался. В конце концов, без модели вы никак не обойдетесь попросту. Вам не кажется что проблема не в MVC изначально? Ведь VIPER и есть MVC, а точнее один из вариантов реализации MVC.
Что мешает выделить логику из «эпловского view controller-a» в отдельные сущности? Вы сильно привязались к самим названиям, а речь в первую очередь о роли класса. Чем презентер в VIPER не контроллер описанный в MVC? Он как раз является посредником между данными и отображением, вполне себе контроллер.
Но то что VIPER говорит КАК выделить слои не делает MVC плохим, ведь MVC не запрещает выделять эти слои. Я не понимаю зачем ругать подход, который ничем не ограничивает разработчика, при этом используя его же.
Что писать, а что нет — решение сугубо индивидуальное. Можно просто следовать хотя бы только SRP и это уже подскажет что и куда. И не придется себя ограничивать различными реализациями и писать лишние слои, когда можно сделать проще. И от этого не пострадает гибкость и тестируемость кода.
ИМХО, Вайпер интересно смотрелся в мире Objc с обязательным .h/.m разделением, но в Swift можно делать и проще и удобнее и лаконичнее.
VIPER – один из способов решить проблему Massive VC и «лапшекода» и я попытался показать это на примере. Напишете свою реализацию архитектуры с примером. Я бы с удовольствием посмотрел.
Пример с пылу-жару: функционал в чатах hold-to-talk. Можно написать в контроллере это и ругать за «массивность», можно сделать то же самое в размазав логику по презентеру и интерактору, а можно выделить модель которая будет описывать поведение и использовать ее, а то что касается UI части, как и писали выше, описать в отдельном controllere и сделать его чайлдом. Сразу и тестируемость, и распределние по отвественности, и отличная переиспользуемость кода.
Cервисы должны быть чистыми функциями первого порядка — не могут хранить никаких данных.
Для данных у тебя есть отдельный слой Model, модели могут быть пассивными — это старый вариант, и активными — этот путь как раз и описывается в MVC по гайдлайну Apple. Один из хороших примеров M слоя — это CoreData, просто закрой ее за сервис, возвращающий Plain Object.
Мне нравится из концепции REDUX возвращать один метод
func update(_ state: MyState)
и через switch - case
обновлять весь UI под новые данные.Когда говорят про VIPER, то почти первым же предложением добавляют — тестируемость и модульность. Но странным образом, когда берутся за реализацию, то unit тесты сразу отпадают, их просто не хотят писать ни по TDD, ни по BDD техникам. Где то была обширная статейка про статистику количество багов на проект, и процент Crash Free от смены богомерского MVC на правильный VIPER не дал перфоманса, даже малейшего.
Следующим пунктом — это модульность и независимость классов друг от друга. Идея, которая должна порадить массу маленьких самодостаточных фреймворков, наподобие компонентов, которые можно использовать между проектами и соблюдая Code Style и UI Style, оказалась утопической и нереализуемой в продакшене.
И чисто из технических подходов — это двунаправленная связь через протоколы легко ломает бизнес логику и приводит к взаимоисключающим состояниям, Presenter раздувается до невообразимых размеров (Massive View Controller перекочевал в слой Presenter ?), а Router вообще плохо ложится на UIKit.
Описывать проблемы использования VIPER в контексте IOS можно бесконечно…
Другими словами — VIPER это один из видов MV(X) архитектуры, но с более дробленном контроллером.
На модули же разделяют не для того, чтобы потом сделать из них маленькие фреймворки и переиспользовать. А больше для того, чтобы легче было тестировать и отлаживать каждый слой в модуле. Проблема с UI? Значит ищем во View или презентере. И т.д. Знаем, в каком слое что искать.
По поводу «протоколы легко ломает бизнес логику и приводит к взаимоисключающим состояниям» не совсем понял. Довольно абстрактно без конкретного примера.
Проблему с роутером я решил в своей реализации. Она использует все родные механизмы. Но зато логику перехода смотрим в роутере. И не надо все это искать во вьюконтроллере.
По поводу дробления контроллера я абсолютно согласен. Про это и статья. Как разбить Massive View Controller.
Буду очень признателен (и не я один), если продемонстрируете свой пример использования Роутера, так как удачный и функциональный способ будет очень полезен для сообщества.
Некоторые принципы Redux так же очень клево ложаться на IOS и UIKit, при этом позволяют обойти многие проблемы роутинга и разделения отвественности, а так же удобно расширяемые по мере роста приложения.
Наиболее близкий вариант VIPER
-а какой я использую у себя на проекте :)
Massive View Controller это давно устаревший анекдот, хватит уже.
В том-то и дело, что имхо не стоит заражать интернет этой болезнью. Неопытный разработчик увидит все эти навороты, решит, что раз непонятно, значит круто, и будет дальше распространять эпидемию. А через месяц такую же статью напишет, "потому что massive view controller". В итоге куча каких-то хрен пойми зачем слоёв, протоколы, и view.setInputValue(with:...) вместо view.inputValue = ...
Или вот extension на UITextField. Там ни одна строчка кода не относится к UITextField, это просто манипуляции над строками. После такого ни на какие вайперы даже смотреть не хочется.
view?.prop1 = val1
view?.prop2 = val2
// хороший программист уже распаковал бы view
view?.setInputValue(with: inputValue) // зачем with:?
func urlButtonClicked(with urlString: String?)
// вместо
func urlButtonClicked(with url: URL)
// и вместо какого-нибудь
func handle(url: URL)
В `getAllCurrencies` если не дай бог придёт не dictionary, то HUD никогда не скроется. и в файле `MainInteractor.swift` вообще сложно понять кто и когда показывает и скрывает HUD, там явно несбалансированные вызовы.
Короче, на практике оказывается, что борцы за архитектуру и чистоту кода борятся не с теми проблемами, что лично меня очень огорчает. Вроде лучше бы промолчать, но уже не могу.
Может сложиться мнение, что я вместо VIPER начал вдруг критиковать автора, но в данном случае, на мой взгляд, это всё взаимосвязанные вещи. По-моему, не очень-то хорошо браться рассказывать о каких-то архитектурах, если в других местах вроде бы на те же самые проблемы закрыты глаза. Чистота кода исходит не из ограничений того паттерна, который ты выбрал, а из самого программиста. Поэтому я склонен думать, что никакой VIPER ничему не поможет, если не научить человека стремлению к логичности, последовательности и критическому мышлению. А если такие качества есть у тебя и твоих коллег, то вы уже сами себе архитекторы – делаете минимально необходимые действия вместо перегоняния одних и тех же данных через протоколы и слои, которые сами же себе понастроили.
Я не пытаюсь оскорблять, скорее хочу донести мысль о том, что обратив внимание на те «мелочи», которые по сравнению с АРХИТЕКТУРОЙ кажутся уже незначительными, можно сильно улучшить качество приложения. Как кода – так и самого приложения, как следствие. Обратить внимание на поток данных, сделать его простым и понятным. Использовать подходящие структуры данных. Я уже обращал внимание на `urlString?` вместо `URL`. Зачем N слоям приложения мучаться с optional строкой, когда там на самом деле URL, ещё и потенциально невалидный? Зачем добавлять код в extension UI элемента, если код только лишь преобразует одни данные в другие? Самое место сделать отдельную функцию, которая принимает String и возвращает String – это уже даст гораздо больше для SRP, тестируемости и прочего XYZ. Начнёшь с маленького, а там и большое не захочется портить.
Это как, я не знаю, ссать в подъезде, но строго в один и тот же угол. Но ни в коем случае не в другой.
В данном примере по правильной и чистой архитектуре вообще нельзя ссать в подъезде, это надо делать в туалете. А как конкретно и чем вы ссыте – это правильные конструкции кода или чистый код.
Я хотел показать, ГДЕ надо писать определенную часть логики, т.е. в каком слое.
Приведите свой пример небольшого приложения для сравнения. Поделитесь с другими, как писать идеальный негрязненький код без никакой архитектуры.
func urlButtonClicked(with urlString: String?) тут String потому что вьюконтроллер не должен заниматься преобразованием в URL, это делается в интеракторе, а View-компонент знает только строки.
func handle(url: URL) нельзя писать во вьюконтроллере. Это уже логика заточенная будет. Мы должны только сообщать презентору, что нажалась кнопка.
В getAllCurrencies идет проверка на dict внутри saveAllCurrencies есть completion(CurrencyError(description: «Currencies' data format is wrong»)). Есть, конечно, опасность, что ни error ни ответа не придет, но там дописать один else только. Но это рабочий пример, а не приложение для продакшна.
Презентер ничего не знает о UIKit, он не знает об UIButton, UILabel и никаких других визуальных компонентах. Это очень важно.
и далее
presenter.currencyPickerView = viewController.currencyPickerView
weak var currencyPickerView: CurrencyPickerViewProtocol?
Презентер знает только это:
protocol CurrencyPickerViewProtocol: class {
var arrayCurrencyNames: [String] { set get }
var title: String { set get }
var selectedCurrencyIndex: Int? { set get }
func reload()
}
CurrencyPickerView: UIView, UIPickerViewDataSource, UIPickerViewDelegate, CurrencyPickerViewProtocol и MainViewController: UIViewController, MainViewProtocol, UITextFieldDelegate
Их можно заменить моками, соответствующим протоколам.
Разбор архитектуры VIPER на примере небольшого iOS приложения на Swift 4