Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Viper - чистая архитектура iOS-приложения (И. Чирков)

3,158 views

Published on

Вопросы, возникающие при использовании MVC, и их решение при помощи VIPER.
1. Проблемы, решаемые VIPER-ом. История появления.
2. Структура VIPER-модуля
3. Сервисы
4. Data flow
5. Навигация
6. Вложенные модули
7. Data flow между модулями
8. Кодогенерация. Vipergen

Published in: Software

Viper - чистая архитектура iOS-приложения (И. Чирков)

  1. 1. VIPER - чистая архитектура iOS приложения Проблемы, возникающие при использовании MVC, и их решение при помощи VIPER.
  2. 2. План Проблемы MVC Структура VIPER модуля Сервисы Data flow в модуле Навигация Data flow между модулями Вложенные модули
  3. 3. Что такое MVC?
  4. 4. Что такое MVC?
  5. 5. MVC - Massive View Controller!
  6. 6. MVC
  7. 7. Чем должен занимается Controller? Обновлять данные на View Ловить события, генерируемые пользователем
  8. 8. MVC в реальности
  9. 9. Чем приходится занимается Controller-у Обновлять данные на View Ловить события, генерируемые пользователем Являться делегатом разнообразных сервисов Обрабатывать полученные данные Отвечать за навигацию между экранами Отвечать за поток данных между экранами
  10. 10. Чем приходится занимается Controller-у Обновлять данные на View Ловить события, генерируемые пользователем Являться делегатом разнообразных сервисов Обрабатывать полученные данные Отвечать за навигацию между экранами Отвечать за поток данных между экранами
  11. 11. Последствия Massive View Controller Огромные классы Нарушение принципов SOLID (куча ответственностей) Сложно дебажить Сложно вносить изменения Сильная связность компонентов Сложно/невозможно тестировать
  12. 12. VIPER Clean Architecture iOS приложения
  13. 13. VIPER View Interactor Presenter Entity Router
  14. 14. Структура VIPER модуля
  15. 15. Структура VIPER модуля
  16. 16. View Presenter Interactor Router override func viewDidLoad() { super.viewDidLoad() self.title = "Title" } func setupTitle(title: String) { self.title = title } override func viewDidLoad() { super.viewDidLoad() presenter.setup() } func setup() { view.setupTitle("Title") }
  17. 17. View Presenter Interactor Router @IBAction func validateTouchUpInside() { let email = self.emailField.text if self.validateEmail(email) { self.presentSuccessScreen() } } @IBAction func validateTouchUpInside() func validateButtonPressed(email: String) func validateEmail(email: String) -> Bool func presentSuccessScreen() if () {}
  18. 18. Сервисы Разбиваем интерактор на сервисы
  19. 19. Сервисы
  20. 20. Data flow
  21. 21. Data flow
  22. 22. Навигация SettingsModule GeneralSettingsModule
  23. 23. SettingsModule GeneralSettingsModule
  24. 24. protocol SettingsDisplayManagerDelegate: class { // DISPLAY MANAGER DELEGATE func didSelectCell() } class SettingsDisplayManager: NSObject, UITableViewDelegate { // DISPLAY MANAGER weak var delegate: SettingsDisplayManagerDelegate? // VIEW func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { delegate?.didSelectCell() } } class SettingsView : UIViewController, SettingsDisplayManagerDelegate { // VIEW var output: SettingsPresenter! var displayManager: SettingsDisplayManager! func didSelectCell() { output.didSelectCell() } }
  25. 25. class SettingsPresenter { // PRESENTER var view: SettingsView! var router: SettingsRouter! func didSelectCell() { router.presentGeneralSetting() } } class SettingsRouter { // ROUTER var view: SettingsView! func presentGeneralSetting() { let generalSettingsModule = GeneralSettingsModule() //Инициализируем модуль GeneralSettingModule let generalSettingsView = generalSettingsModule.view // Забираем у него View view.navigationController?.pushViewController(generalSettingsView, animated: true) } }
  26. 26. DataFlow между модулями
  27. 27. NewsModule DetailModule NewsID
  28. 28. NewsModule DetailModule [NewsItem] [NewsItem] [NewsItem] [NewsItem]NewsID NewsID NewsID NewsID NewsIDNewsItem NewsItem Done
  29. 29. NewsModule DetailModule NewsID NewsID
  30. 30. NewsModule DetailModule NewsID NewsID outputHandler outputHandler
  31. 31. class NewsPresenter: DetailModuleOutput { // PRESENTER func didSelectNews(newsId: Int) { router.presentDetails(self) } func newsAddedToFavorites(newsId: Int) { } } class NewsRouter { // ROUTER var view: NewsView! func presentDetails(outputHandler: DetailModuleOutput) { let detailModule = DetailModule(outputHandler: DetailModuleOutput)//Инициализируем модуль let detailModuleView = detailModule.view // Забираем у него View view.navigationController?.pushViewController(detailModuleView, animated: true) } } protocol DetailModuleOutput: class { // OutputHandler Protocol func newsAddedToFavorites(newsId: Int) }
  32. 32. class DetailPresenter { // PRESENTER weak var outputHandler: DetailModuleOutput? func newsAddedToFavorite(newsId: Int) { // Метод вызван интерактором (DetailInteractor) outputHandler?.newsAddedToFavorites(newsId) } } class NewsPresenter: DetailModuleOutput { // PRESENTER func didSelectNews(newsId: Int) { router.presentDetails(self) } func newsAddedToFavorites(newsId: Int) { // DONE! } }
  33. 33. Вложенные модули
  34. 34. Данные мастера Портфолио Услуги Расписание Отзывы
  35. 35. Немного кода
  36. 36. Инициализация модуля let newsModule = NewsModule() //Инициализируем модуль NewsModule let newsView = newsModule.view // Забираем у него View view.navigationController?.pushViewController(newsView, animated: true)
  37. 37. class NewsModule: NSObject { private var viewController: NewsViewController? var view: UIViewController { guard let view = viewController else { viewController = NewsViewController(nibName: "NewsViewController", bundle: nil) configureModule(viewController!) return viewController! } return view } private func configureModule(view: NewsViewController) { // Устанавливает зависимости модуля. let presenter = NewsPresenter() let router = NewsRouter() let interactor = NewsInteractor() router.view = view view.output = presenter view.router = router presenter.view = view presenter.router = router presenter.interactor = interactor interactor.output = presenter } }
  38. 38. Viewprotocol NewsViewInput: class { func updateView(news: [NewsItem]) } protocol NewsViewOutput: class { func setupView() } class NewsViewController: UIViewController, NewsViewInput, NewsDisplayManagerDelegate { var output: NewsViewOutput! // Ссылка на Presenter let displayManager = NewsDisplayManager() override func viewDidLoad() { super.viewDidLoad() output.setupView() // View сообщает Presenter-у о готовности } func updateView(news: [NewsItem]) { // View получила от Presenter-а массив моделей новостей displayManager.updateTable(news) } }
  39. 39. Presenterclass NewsPresenter: NewsViewOutput, NewsInteractorOutput { weak var view: NewsViewInput! var router: NewsRouter! var interactor: NewsInteractorInput! func setupView() { interactor.obtainNews() // Presenter запрашивает список новостей у Interactor-a } func newsObtained(cards: [CardItem]) { if news.count == 0 { view.showPlaceholder() } else { view.hidePlaceholder() view.updateView(news) // Presenter отправляет список полученных новостей View-шке } } }
  40. 40. Interactor protocol PaymentInteractorInput: class { func obtainNews() } protocol PaymentInteractorOutput: class { func newsObtained(news: [NewsItem]) func errorReceived(message: String) } class NewsInteractor: NewsInteractorInput { weak var output: NewsInteractorOutput! let newsService = NewsService() func obtainNews() { newsService.obtainNews( success: { news in self.output.newsObtained(news) }, failure: { error in self.output.errorReceived(error.localizedDescription) } ) } }
  41. 41. Routerclass NewsRouter { weak var view: UIViewController! func presentDetails(newsId: Int) { let detailModule = DetailModule() //Инициализируем модуль NewsModule let detailView = detailModule.view // Забираем у него View view.navigationController?.pushViewController(detailView, animated: true) } func presentError(message: String) { let alert = UIAlertController(title: "ERROR_TITLE".localized, message: message, preferredStyle: .Alert) alert.addAction(UIAlertAction(title: "OK".localized, style: .Default, handler: nil)) view.presentViewController(alert, animated: true, completion: nil) } }
  42. 42. Файлы VIPER модуля ViewInputProtocol ViewOutputProtocol View Presenter Router InteractorInputProtocol InteractorOutputProtocol Interactor Итого: 8 файлов на один модуль! о_О
  43. 43. Кодогенерация. Vipergen
  44. 44. Vipergen https://github.com/nsleader/vipergen
  45. 45. VIPER Clean Architecture iOS приложения

×