Что такое viper: Разбор архитектуры VIPER на примере небольшого iOS приложения на Swift 4 / Хабр

Содержание

Разбор архитектуры VIPER на примере небольшого iOS приложения на Swift 4 / Хабр

«У каждого свой VIPER». Автор неизвестен
В данной статье я хотел бы рассмотреть архитектуру VIPER на небольшом конкретном примере, который в того же время показывал всю мощь этой архитектуры и был написан на последнем Swift 4. Для тех, кто хочет сразу глянуть код, не читая всю статью, ссылка на реп в самом низу.



Оглавление




Вступление


Про архитектуру VIPER писали уже достаточно много, в том числе и на хабре (1, 2). Поэтому я решил не отставать от других и написать очередное «полезное» руководство. Все началось с того, что эппловская MVC архитектура оказалась не очень удачной, о чем более подробно рассказывается в этой статье. Если вкратце, то MVC превратился в Massive View Controller, т.е. огромный вьюконтроллер, в котором ему позволялось очень много. В нем было много UI и бизнес-логики, и, как следствие, такой код почти невозможно было тестировать, отлаживать и поддерживать.

Поэтому разработчикам понадобилась другая архитектура, которая была бы более-менее гибкой, соответствовала SOLID принципам (особенно «Принципу единственной ответственности») и чистой архитектуре (обзор на русском).

Rambler подхватили эту тему и посвятили этому целую конференцию и даже написали книгу. Кстати, если вы не знакомы с VIPER, я бы порекомендовал прочитать именно эту книгу, как знакомство с архитектурой. В ней хорошо описано и разжевано, для чего нужен этот VIPER и как появился. Также в книге рассматриваются проблемы классического VIPER, и что разработчики Rambler в ней немного изменили. К сожалению, книга была написана в 2016 году и примеры в ней на

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

Поэтому я написал небольшое приложение «Конвертер валют» на VIPER архитектуре, чтобы показать, что нужно писать в каждом слое и какие правила задаются для каждого слоя. Сразу следует сказать, что я использовал не т.н. классический VIPER, а его немного модифицированную версию, вдохновившись опытом Rambler и по их примеру.

Попрошу сильно не придираться, если стилистически на Swift что-то можно было написать более элегантно. Все же статья про архитектуру, а не про красоту самого Swift. Также я намеренно не стал использовать сторонние библиотеки и зависимости. Весь пример написан, используя только родные для iOS библиотеки.

Глава 0. Схема архитектуры VIPER


Пробежимся бегло по принципам VIPER. Один экран или точнее один вьюконтроллер должен соответствовать одному модулю в VIPER. Если в общем, то VIPER призван разбить многострадающий вьюконтроллер на множество слоев, где каждый будет выполнять свою роль. Схематично связь внутри модуля показана на рисунке снизу.

Вероятно, вы видели другие схемы.

Эту, к примеру:

Каждая буква из аббревиатуры VIPER на ней что-то обознает: View–Interactor–Presenter–Entity–Router. Но реальность такова, что в модуль входят не только эти компоненты, а Entity вообще в понятие модуля может не входить, т.к. является самодостаточным классом, который может использоваться в любом модуле или сервисе. На сложных экранах модуль можно делить на подмодули, где у каждого будут свои презентеры и интеракаторы.

В отличии от классического VIPER в моем нет Wireframe, потому что он выполнял 2 роли: выполнял сборку модуля и осуществлял переход на другой экран (модуль). На схеме показано, что за сборку модуля будет отвечать Configurator, а за переходы Router. Такую логику я перенял у Rambler, с той лишь разницей, что вместо Configurator у них Assembly. Но суть такая же.

Configurator знает о всех зависимостях внутри модуля. В нем устанавливается, что у ViewController будет Presenter

, у Presenter будет Interactor и т. д. Более подробно будет рассматриваться далее в примере.

Также в классическом VIPER отказались от Segue, поэтому вы не сможете использовать сториборды для переходов между экранами. В нашем же случае, как и у Rambler, переходы через Segue работают и являются рекомендуемыми для использования, как того хотела Apple.

Так уж получилось, что на 100% пассивную View из вьюконтроллера сделать не получится. Сама Apple заложила для нее определенную роль со своим циклом жизни и вызываемыми методами (viewDidLoad, viewDidAppear и др.), поэтому мы должны это учитывать и строить свою архитектуру, исходя из этого. Сборка модуля запускается из viewDidLoad, когда вьюконтроллер уже загрузился, а не просто инициализировался. Также это дает нам возможность задавать

Initial View Controller из сториборда, а не в AppDelegate, как это сделано в классическом варианте. Это гораздо удобней, потому что нет жесткой привязки к какой-то конкретной точке входа, и ее легко можно поменять.

После сборки модуля дальнейшее поведение модуля довольно классическое. View/ViewController не отвечает за логику нажатий на кнопки, ввода текста или какое-либо другое взаимодействие с UI. Все это сразу передается в Presenter. View может быть как в составе модуля, так и быть общей View, и использоваться в разных модулях.

Presenter решает, куда перенаправить действие – на Router или Interactor. Router будет либо закрывать текущий экран, либо открывать новый. Конкретная реализация перехода осуществляется в нем. Interactor решает, что делать дальше с поступившими событиями и какой сервис вызвать. В нем содержится логика модуля.

Но более важной функцией Presenter является подготовка и передача визуальных данных для View/ViewController, которые будут видны для пользователя. Presenter является сердцем нашего модуля, он знает, какие данные будут отображаться и в каком виде. Даже на разных схемах он всегда посередине.Interactor, наверно, мозгами)

Interactor является фасадом для других сервисов. Также Interactor может и сам содержать логику. В MVC его можно сравнить с контроллером, но который ничего не знает о том, как будут отображаться данные.

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

Entity. Entity – это просто пассивные сущности (пользователь, книга, слово). Как и говорили ранее, Entity не является компонентом модуля VIPER. Вообще, изначально архитектура называлась VIP.

Если вы ничего не поняли, не беда. Дальше на примере все станет ясно, это было лишь поверхностное описание.

Глава 1. Пример очень простого модуля


Как ни странно, но рассматривать архитектуру я начну не с первого более сложного экрана, а с экрана «О приложении», который очень простой. Сам экран имеет пару лейблов, кнопку «Закрыть» и кнопку со ссылкой на сайт. При нажатии на «Закрыть» текущий экран закроется и будет показан предыдущий главный экран, а при нажатии на ссылку она откроется в Сафари. Лейблы пассивные и не меняются.

Такие экраны в приложении не показывают всю мощь и необходимость VIPER, ведь можно было все разместить и во ViewController, как могут подумать некоторые. Но идеология чистой архитектуры противоречит этому принципу, поэтому даже самый простой экран и даже самое простое приложение можно и нужно писать на архитектуре VIPER. Вы должны придерживаться правил всегда.

Названия модуля желательно выбирать коротким, потому что внутри модуля для классов к этому названию будут прибавляться дополнительные слова. К примеру, модуль «О приложении» назовем About. Вьюконтроллер будет называться AboutViewController. Остальные классы AboutPresenter, AboutInteractor, AboutConfigurator и т.д.

Если инициализация модуля начинается с вьюконтроллера, то и рассматривать модуль надо начинать с него. Создадим классы AboutViewController и AboutConfigurator. Класс AboutConfigurator должен соответствовать протоколу AboutConfiguratorProtocol и будет иметь лишь один метод:

protocol AboutConfiguratorProtocol: class {
    func configure(with viewController: AboutViewController)
}

class AboutConfigurator: AboutConfiguratorProtocol {
    func configure(with viewController: AboutViewController) {

    }
}

В дальнейшем внутри этого метода я буду конфигурировать модуль. AboutViewController будет иметь свойство configurator, который во viewDidLoad будет конфигурироваться, и свойство presenter, который будет соответствовать протоколу AboutPresenterProtocol.

Важное правило! Все компоненты общаются между собой только через протоколы, а не напрямую! Это необходимо для написания юнит-тестов в дальнейшем и для поддержания кода в чистоте в целом.

AboutPresenterProtocol должен содержать метод configureView(), который будет инициализировать и конфигурировать первоначальные данные для визуальных элементов во вьюконтроллере.

На данном этапе AboutViewController будет выглядеть так:

class AboutViewController: UIViewController {
        
    var presenter: AboutPresenterProtocol!
    let configurator: AboutConfiguratorProtocol = AboutConfigurator()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        configurator.configure(with: self)
        presenter.configureView()
    }
}

Presenter будет иметь также router и методы, которые будут вызываться при нажатии на кнопку «Закрыть» и кнопку со ссылкой на сайт. AboutPresenterProtocol будет выглядеть так:
protocol AboutPresenterProtocol: class {
    var router: AboutRouterProtocol! { set get }
    func configureView()
    func closeButtonClicked()
    func urlButtonClicked(with urlString: String?)
}

Модуль этот очень простой, поэтому вся конфигурация вьюконтроллера будет заключаться в том, что подпись к кнопке с URL будет устанавливаться из кода, а не из визуального редактора.
Для AboutViewController такой протокол:
protocol AboutViewProtocol: class {
    func setUrlButtonTitle(with title: String)
}

Внутри AboutPresenter реализовываем метод:
func configureView() {
     view.setUrlButtonTitle(with: interactor.urlRatesSource)
}

Теперь подошла очередь и интерактора. Логику и хранение/извлечение данных всегда надо переносить туда. В нашем случае интерактор будет иметь свойство, которое будет хранить URL сайта и метод, который будет открывать этот URL:
protocol AboutInteractorProtocol: class {
    var urlRatesSource: String { get }
    func openUrl(with urlString: String)
}

А как же обработка события нажатия на кнопку «Закрыть», можете подумать вы? Здесь презентер решает, что это событие связано с переходом между экранами, поэтому обработка будет передаваться в роутер. Для этого модуля роутер умеет только закрывать текущий вьюконтроллер.

Его протокол:

protocol AboutRouterProtocol: class {
    func closeCurrentViewController()
}

А протокол презентера будет выглядеть так:
protocol AboutPresenterProtocol: class {
    var router: AboutRouterProtocol! { set get }
    func configureView()
    func closeButtonClicked()
    func urlButtonClicked(with urlString: String?)
}

Теперь, когда у нас есть все описанные протоколы для компонентов модуля VIPER, перейдем к самой реализации этих методов. Не забудем дописать, что вьюконтроллер соответствует протоколу AboutViewProtocol. Я не буду описывать, как кнопку со сториборда связать со свойством вьюконтроллера и привязать события нажатия на них, поэтому сразу напишу методы вьюконтроллера:
@IBOutlet weak var urlButton: UIButton!

@IBAction func closeButtonClicked(_ sender: UIBarButtonItem) {
    presenter.closeButtonClicked()
}
    
@IBAction func urlButtonClicked(_ sender: UIButton) {
    presenter.urlButtonClicked(with: sender.currentTitle)
}
        
func setUrlButtonTitle(with title: String) {
    urlButton.setTitle(title, for: .normal)
}

Вьюконтроллер понятия не имеет, что делать после нажатия на кнопки, но он точно знает, что делать, когда у него вызвали метод setUrlButtonTitle(with title: String). Вьюконтроллер только обновляет, передвигает, перекрашивает, скрывает UI-элементы на основе данных, с которыми презентер вызвал этот метод. В то же время презентер не знает, как именно все эти данные располагаются во View/ViewController.

Полный класс презентера выглядет так:

class AboutPresenter: AboutPresenterProtocol {
    
    weak var view: AboutViewProtocol!
    var interactor: AboutInteractorProtocol!
    var router: AboutRouterProtocol!
    
    required init(view: AboutViewProtocol) {
        self.view = view
    }
    
    // MARK: - AboutPresenterProtocol methods
    
    func configureView() {
        view.setUrlButtonTitle(with: interactor.urlRatesSource)
    }
    
    func closeButtonClicked() {
        router.closeCurrentViewController()
    }
    
    func urlButtonClicked(with urlString: String?) {
        if let url = urlString {
            interactor.openUrl(with: url)
        }
    }
}

Мы совсем забыли про наш конфигуратор. Ведь без него ничего работать не будет. Его код:
class AboutConfigurator: AboutConfiguratorProtocol {
    
    func configure(with viewController: AboutViewController) {
        let presenter = AboutPresenter(view: viewController)
        let interactor = AboutInteractor(presenter: presenter)
        let router = AboutRouter(viewController: viewController)
        
        viewController.presenter = presenter
        presenter.interactor = interactor
        presenter.router = router
    }
}

Понятное дело, чтобы не получить Reference cycle, презентер у вьюконтроллера указывается как strong, а вьюконтроллер у презентера как weak, интерактор у презентера указывается как weak, ну и так далее. Во всей этой цепочке самым главным остается ViewController. Поэтому говорить о пассивном View здесь неуместно. При закрытии ViewController все остальные элементы тоже уничтожаются, потому что никто не может иметь strong ссылку на ViewController. В противном случае мы бы получали утечку памяти (memory leak).

Класс интерактора выглядет так:

class AboutInteractor: AboutInteractorProtocol {
    
    weak var presenter: AboutPresenterProtocol!
    let serverService: ServerServiceProtocol = ServerService()
    
    required init(presenter: AboutPresenterProtocol) {
        self.presenter = presenter
    }
    
    var urlRatesSource: String {
        get {
            return serverService.urlRatesSource
        }
    }
    
    func openUrl(with urlString: String) {
        serverService.openUrl(with: urlString)
    }
}

Код довольно простой, поэтому комментарии излишни. Стоит обратить внимание на ServerService. Это сервис, который будет отдавать URL для нашей кнопки на вьюконтроллере и открывать ссылку в Сафари (или как-нибудь по другому). Код ServerService и его протокола выглядет так:
protocol ServerServiceProtocol: class {
    var urlRatesSource: String { get }
    func openUrl(with urlString: String)
}

class ServerService: ServerServiceProtocol {
    
    var urlRatesSource: String {
        return "https://free.currencyconverterapi.com"
    }
    
    func openUrl(with urlString: String) {
        if let url = URL(string: urlString) {
            UIApplication.shared.open(url, options: [:])
        }
    }
}

Здесь тоже все просто. Остался только роутер:
class AboutRouter: AboutRouterProtocol {
    
    weak var viewController: AboutViewController!
    
    init(viewController: AboutViewController) {
        self.viewController = viewController
    }
    
    func closeCurrentViewController() {
        viewController.dismiss(animated: true, completion: nil)
    }
}

Еще раз повторю, что весь исходный код есть в репозитории. Ссылка в конце статьи.

Глава 2. Пример более сложного модуля


Настало время рассмотреть архитектуру на более сложном примере и подытожить правила для каждого слоя.

Сториборд со всеми экранами выглядет так. Главный экран позволяет выбирать валюту, из которой конвертируем и в которую конвертируем. Также можно вводить сумму, из которой надо сконвертировать в другую валюту. Под полем ввода отображается сконвертированная в другую валюту сумма. А в самом низу курс конвертации и кнопка перехода на экран «О приложении«.

Данные о всех валютах и их курсе запрашиваются с бесплатного сайта https://free.currencyconverterapi.com. Данные для простоты примера будем хранить в UserDefaults, но запросто можно переделать все лишь один класс, чтобы хранить их в Core Data или любым другим способом.

Теперь, когда мы знаем, как выглядет каркас модуля VIPER, мы запросто сможем то же самое сделать для главного экрана. Справа показаны файлы модуля Main (главного экрана). Небольшим неудобством архитектуры является то, что для каждого модуля нужно создавать много файлов, а вручную на это уходит много времени. Поэтому в Rambler придумали генератор кода Generamba, который выполняет рутинную работу за нас. Если покопаться, то его можно настроить под себя. Либо же можно использовать шаблоны для Xcode, примеров в сети предостаточно. Например, https://github.com/Juanpe/Swift-VIPER-Module или https://github.com/infinum/iOS-VIPER-Xcode-Templates. Более подробно эти темы рассматриваться не будут, т.к. это выходит за рамки статьи.

В глаза бросается то, что для протоколов я создал отдельный в файл в каждом модуле. Если вам не нравится такой подход, то вы можете описывать протоколы прям в файле самих классов. Лично мне удобней, когда все протоколы модуля собраны в одном месте. Дело вкуса.

По традиции также начнем рассматривать модуль с вьюконтроллера. Важным правилом для View/ViewController является то, что в них не передаются Entity напрямую. Для этого должны создаваться дополнительные слои/сервисы.

Метод viewDidLoad будет идентичен реализации из модуля About. Вызовется конфигурирование модуля и будет дана команда интерактору сконфигурировать View (ViewController в нашем случае).

Конфигурирование модуля почти такое же, как в модуле «About». Но на главном экране понадобиться дополнительный View-компонент CurrencyPickerView, он создается как отдельный класс и может быть переиспользован в других местах или даже приложениях. В сториборде на главном экране добавляется обычный UIView и выставляется класс CurrencyPickerView для него.

Весь код CurrencyPickerView рассматриваться не будет. Хотя IBOutlet для него находится во вьюконтроллере, его логика будет обрабатываться в презентере. Поэтому в конфигураторе прописывается ссылка на него. У CurrencyPickerView также есть делегат, и им будет не вьюконтроллер, а презентер. В конфигураторе дописываем следующие вызовы:

class MainConfigurator: MainConfiguratorProtocol {
    
    func configure(with viewController: MainViewController) {
        ...
        presenter.currencyPickerView = viewController.currencyPickerView
        viewController.currencyPickerView.delegate = presenter
    }
}

На более сложном примере становится ясно, что вьюконтроллер разгружается, а логика переносится от презентера к интерактору и дальше к сервисам. Конфигурирование View в этом модуле более сложное и включает установку сохраненных значений, которые использовались последний раз до закрытия приложения. Код такой:
func configureView() {
    view?.setInputValue(with: inputValue)
    view?.setOutputValue(with: outputValue)
    view?.setInputCurrencyShortName(with: inputCurrencyShortName)
    view?.setOutputCurrencyShortName(with: outputCurrencyShortName)
    view?.addDoneOnInputCurrencyKeyboard()
    updateRateText()
    interactor.getAllCurrencies()
}

Помимо установки начальных значений для UI-компонентов, в интерактор посылается запрос о получении списка всех валют. Презентер не знает, откуда будут получены эти данные, но он знает, что они ему нужны. Также значения inputValue, outputValue, inputCurrencyShortName и outputCurrencyShortName запрашиваются у интерактора, т.к. только он знает, откуда взять эти сохраненные данные:
var inputValue: String? {
    set {
        if let value = newValue {
            interactor.inputValue = Double(value) ?? 0.0
        }
    }
    get {
        var input = String(interactor.inputValue)
        if input.hasSuffix(".0") {
            input.removeLast(2)
        }
        return input
    }
}
var outputValue: String? {
    get {
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
        formatter.maximumFractionDigits = 2
        formatter.roundingMode = .down
        formatter.usesGroupingSeparator = false
        let number = NSNumber(value: interactor.outputValue)
        var output = formatter.string(from: number)!
        
        if output.hasSuffix(".00") {
            output.removeLast(2)
        }
        return output
    }
}
var inputCurrencyShortName: String {
    get {
        return interactor.inputCurrencyShortName
    }
}
var outputCurrencyShortName: String {
    get {
        return interactor.outputCurrencyShortName
    }
}

В комментариях к VIPER я встречал такое мнение, что презентер особо ничего не делает и просто передает данные от вьюконтроллера к интерактору и обратно. Из кода выше становится ясно, что презентер не просто запрашивает данные у интерактора и отдает «как есть», а также выполняет подготовку и форматирование данных в нужном виде. Запомните, что презентер отвечает за то, какие именно данные и в каком виде будут переданы вьюконтроллеру. Вьюконтроллеру уже не надо заботится об их форматировании, он лишь присвоит их нужным UI-компонентам.

Презентер ничего не знает о UIKit, он не знает об UIButton, UILabel и никаких других визуальных компонентах. Это очень важно. Вся работа с UIKit происходит во вьюконтроллерах и других View-компонентах. Также и при нажатии на кнопку нельзя передавать параметром UIButton, презентер не должен знать об этом. Поэтому нажатия на кнопки и ввод текста в поле ввода обрабатываются во вьюконтроллере таким образом:

@IBAction func inputCurrencyButtonClicked(_ sender: UIButton) {
    presenter.inputCurrencyButtonClicked()
}

@IBAction func outputCurrencyButtonClicked(_ sender: UIButton) {
    presenter.outputCurrencyButtonClicked()
}

func textFieldDidBeginEditing(_ textField: UITextField) {
    presenter.textFieldDidBeginEditing()
}

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    
    if textField == inputTextField {
        if textField.availableAdding(string: string) {
            textField.addString(string)
            self.presenter.inputValueChanged(to: textField.text ?? "")
        }
        return false
    }
    return true
}

func textFieldShouldClear(_ textField: UITextField) -> Bool {
    
    if textField == inputTextField {
        textField.clear()
        self.presenter.inputValueCleared()
        return false
    }
    return true
}

Допустим, надо написать кастомный UI-элемент или дописать extension для готового. Рассмотрим, например, UITextField из главного модуля. Компонент может содержать свою внутреннюю логику, касающуюся только его. К примеру, поле ввода суммы валюты может быть дробным, но нельзя ввести 2 нуля подряд в начале («00») или вводить несколько точек («0.11.2»), запятая преобразуется в точку, вводить можно только числа, добавляется дополнительная кнопка к клавиатуре и т.д. В таком случае разрешается эту логику выносить в сам элемент. Ведь эта логика не затрагивает логику других компонентов приложения, а относится только к нему самому. Например, вот так:
extension UITextField {
    
    func availableAdding(string: String) -> Bool {
        switch string {
        case "":
            return self.text != ""
        case "0"..."9":
            return self.text != "0"
        case ".", ",":
            return self.text!.count > 0 && self.text!.range(of: ".") == nil && self.text!.range(of: ",") == nil
        default:
            return false
        }
    }
    
    func addString(_ string: String) {
        var newValue: String = self.text ?? ""
        var addingString = string
        if addingString == "", newValue.count > 0 {
            newValue.removeLast()
        } else if addingString != "" {
            if addingString == "," {
                addingString = "."
            }
            newValue.append(addingString)
        }
        self.text = newValue
    }
    
    func clear() {
        self.text = ""
    }
    
    func addDoneOnKeyboard() {
        let keyboardToolbar = UIToolbar()
        keyboardToolbar.sizeToFit()
        let flexBarButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
        let doneBarButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissKeyboard))
        keyboardToolbar.items = [flexBarButton, doneBarButton]
        self.inputAccessoryView = keyboardToolbar
    }
    
    @objc func dismissKeyboard() {
        self.resignFirstResponder()
    }
}

Другое дело, когда такая логика может затрагивать внешние данные, например, доступность логина для юзера при регистрации, т.к. будет запрос к серверу или базе данных. Или повтор пароля, ведь это уже затронет другой компонент. В общем, в UI-компонент можно вносить логику, которая относится только к нему самому. Хотя для кого-то это может показаться спорным моментом.

Рассмотрим, как происходит получение данных о валютах с сервера. В презентере произошел вызов метода getAllCurrencies() и все. Что должно происходить дальше, презентер не знает. Дальнейшая логика пишется в интеракторе и там, по необходимости, посылаются команды презентеру, что нужно делать. Реализация такая:

func getAllCurrencies() {
    presenter.showHUD()
    serverService.getAllCurrencies { (dict, error) in
        
        if let error = error {
            self.presenter.hideHUD()
            self.presenter.showLoadCurrenciesButton()
            self.presenter.showAlertView(with: error.localizedDescription)
            return
        }
        
        if let dictResponse = dict {
            self.currencyService.saveAllCurrencies(with: dictResponse, completion: { (error) in
                
                if let error = error {
                    self.presenter.hideHUD()
                    self.presenter.showAlertView(with: error.localizedDesc)
                    return
                }
                self.currencyService.sortAndUpdateCurrentCurrencies()
                self.getOutputCurrencyRatio(newCurrency: nil)
            })
        }
    }
}

В этом интеракторе задействованы уже 2 сервиса: CurrencyService, который отвечает за работу с валютами, и ранее известный ServerService, который отвечает за работу с сервером. Конечно, ServerService было бы правильней разбить на несколько классов-сервисов, чтобы переиспользовать методы работы не с конкретным сервером, а с любым. Но я ограничился здесь одним классом, чтобы упростить и не создавать по миллион классов на все, ведь для архитектуры модулей VIPER это не повредит.

Для сервисов тоже очень желательно создавать протоколы, чтобы работать через них и не привязываться к конкретным классам, согласно одному из принципов SOLID принципу инверсии зависимостей.

Сервис CurrencyService будет помогать работать с такой структурой данных, как Currency (валюта). Он будет отдавать все доступные валюты, текущее введенное значение для валюты, типы валют, а также уметь сохранять, сортировать и получать курс относительно двух валют. Его протокол выглядет так:

protocol CurrencyServiceProtocol: class {
    var currencies: [Currency] { set get }
    var currencyNames: [String] { set get }
    var inputValue: Double { set get }
    var outputValue: Double { get }
    var inputCurrency: Currency { set get }
    var outputCurrency: Currency { set get }
    func saveAllCurrencies(with dict: [String: Any], completion: @escaping (CurrencyError?) -> Swift.Void)
    func sortAndUpdateCurrentCurrencies()
    func saveOutputCurrencyRatio(with dict: [String: Any], completion: @escaping (CurrencyError?) -> Swift.Void)
} 

Сервис CurrencyService запрашивает данные у другого сервиса StorageService, который сохраняет данные в UserDefaults, а интерактор даже и не подозревает, что данные вообще сохраняются, не говоря о том, как они сохраняются. Интерактор даже не знает, что существует сервис StorageService, потому что сервис-хелпер CurrencyService выполняет всю работу сам и только отдает данные интерактору.

Интерактор главного модуля в основном состоит из абстракций и вызовов к методам протоколов сервисов, поэтому для таких слоев очень легко писать юнит-тесты, т.к. создать моки и стабы для них не составит большого труда. Нет привязки к конкретным классам.

Я не буду дальше подробно рассматривать каждый метод и каждую строчку оставшихся классов. Общая суть архитектуры и роль каждого слоя уже должна быть ясна. Последнее, о чем хотел бы сказать, это переход на другой экран через Segue.

Apple заложила этот функционал для вьюконтроллера, поэтому и нам надо исходить из такого подхода, а не переходить через navigationController?.pushViewController(vc, animated: true). После нажатия на кнопку «Инфо» должен открыться экран «О приложении». Поэтому событие нажатия кнопки презентер передает роутеру, а он вызывает следующий метод:

func showAboutScene() {
    viewController.performSegue(withIdentifier: viewController.selfToAboutSegueName, sender: nil)
}

Вьюконтроллер вызывает системный prepare(for segue…), а он уже напрямую передает это событие в роутер:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    presenter.router.prepare(for: segue, sender: sender)
}

Получилось, что презентер минуется в этом случае. Если кому-то не нравится такое нарушение, то надо либо пропускать метод через презентер, передавая как параметр UI-элемент, что будет тоже нарушением. Либо придумать что-нибудь еще. Лучшего варианта я сам пока не нашел. Если бы можно устанавливать делегат для prepare(for: segue, sender: sender), то, конечно, им был бы роутер.

Заключение


Указанный вариант архитектуры не является идеальным. Недаром в начале статьи я привел изречение, что у каждого свой VIPER. Сколько людей, столько и мнений. Я встречал варианты, когда несколько модулей группировали в рамках одного юзер-стори и писали один роутер для нескольких модулей. Или в другом варианте один интерактор на несколько модулей. Многие используют классический вариант с Wireframe, другие придумывают что-то еще. Кто-то передает во вьюконтроллер Entity. Последнее, конечно, неправильно.

Даже если у вас есть написанное как попало приложение, VIPER позволяет переписывать все постепенно. Вьюконтроллер за вьюконтроллером. Это же презентационный слой и каждый модуль не зависит от архитектуры и реализации другого. Начните переносить логику в сервисы постепенно. Разгружайте вьюконтроллер. И в дальнейшей поддержке кода такое разделение по слоям вам многократно окупится.

В статье я не затронул Dependency Injection в модулях для iOS, например, Typhoon. И еще много других свистоперделок дополнительных и полезных вещей, облегчающих разработку. Общее поведение для модулей можно было вынести в абстрактные классы и протоколы, а потом наследоваться от них. В общем, любой проект и код можно улучшать до бесконечности и он все-равно не будет идеальным.

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

Вероятно, статья получилась немного сумбурной и объемной, но для того я и предоставил весь исходный код, чтобы вы сами во всем разобрались. Конструктивная критика и обсуждение приветствуется. Возможно, я что-то неправильно написал. Пишите в комментариях.

Ссылка на репозиторий.

VIPER Swift — BytePace

На бумаге всё выглядит очень просто и понятно, однако на деле приходится сталкиваться с трудностями.

Первая и основная проблема это установление зависимостей между частями модуля (между Presenter, Interactor и т. п.). Подходов к решению этой проблемы существует два:
1) прописывание всех зависимостей вручную
2) использование библиотек для инъекции зависимостей (например, Typhoon).

Первый вариант является наиболее сложным и затратным по времени. Нужно всё верно связать и сделать так, чтобы не было утечек памяти. Из-за чего появляются утечки? Из-за двойных связей — Router имеет ссылку на Presenter, а Presenter, в свою очередь, на Router. Точно так же обстоит дело с View и Presenter. Для того чтобы избежать этой проблемы, нужно в одной из связей использовать weak-ссылку на объект (Presenter на View, Presenter на Router). Это позволит уйти от циклической зависимости и уничтожить объект, когда он будет больше не нужен. Главный плюс данного метода — всё прозрачно: вы полностью контролируете время жизни объектов.

Использование сторонних библиотек должно упростить жизнь разработчику и позволить ему не переживать за зависимости. Однако появляется другая проблема — сторонние библиотеки имеют свойство меняться или вообще исчезать. Учитывая, что данная библиотека будет отвечать за связность проекта, ее потеря будет катастрофической, придется либо искать другую библиотеку, либо делать всё вручную. При изменении библиотеки возможно придется что-то переписывать в коде. В любом случае, это займет немало времени.

Другая беда, приходящая при использовании VIPER, заключается в том, что многие сторонние библиотеки и некоторые стандартные средства языка (UITableView) не пригодны для этой модели. Чаще всего нарушается принцип единой ответственности. С одним из таких нарушений нам пришлось столкнуться на нашем проекте: до перевода на VIPER мы использовали библиотечный textField, в котором был метод, проверяющий, является ли содержимое email адресом. Вызывался он обращением к экземпляру этого класса. Получалось, что логика находилась внутри раздела View. Решений было несколько:
 — пренебречь принципом единой ответственности и оставить всё как есть;
 — передать этот textField в Interactor, но это непростой объект, следовательно, снова нарушение VIPER;
 — избавиться от этой библиотеки и написать проверку самому внутри Interactor.

В данной ситуации это маленькая проблема, которая решилась довольно быстро (мы пошли последним путем, написав всё сами), однако бывают ситуации куда более серьезные, требующие бо́льших умственных и временных затрат.

Вейпер или вайпер? Как правильно нужно называть устройство Vape

Содержание:

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

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

Необходимость рассмотрения вопроса

В XXI столетии многие люди перешли с традиционных сигарет на электронные. Это позволяет наслаждаться большими облаками дыма с приятным вкусом. Впервые появились вейпы в Китае, однако сегодня они популярны во всем мире. Они были разработаны для того, чтобы помочь людям отказаться от курения табака. Новое устройство было названо «электронной сигаретой».

Постепенно популярность этого гаджета возрастала, поскольку было доказано, что рассматриваемый девайс намного безопаснее обычных сигарет. Устройство характеризуется множеством достоинств, таких как экологичность, удобство, безопасность для здоровья человека и другие. Именно большой список плюсов электронной сигареты послужил её распространению по всему миру.

Молодое общество любит использовать в своей речи новые, лаконичные слова, поэтому редко можно услышать у подростков сочетание «электронная сигарета». Чаще звучит термин «вейп», а люди, которые курят элетронку, называют себя «вейперами». Тем не менее, многие люди ошибочно говорят «вайпер», не подразумевая, что этот термин имеет совершенно иное значение.

Вайпер

Термин «Вайпер» имеет различные значения, не относящихся к сленгу:

  1. Торговая марка Viper, которая занимается выпуском различной мототехники. Над продукцией от данного производителя работают лучшие заводы Китая и итальянская студия промышленного дизайка Italdesign.
  2. Суперкар от Dodge, именуемый Viper. Это двухместная, мощная машина, которая была выпущена в 1992 году. Она практически сразу завоевала популярность. Высокий спрос на этот автомобиль сохраняется по сегодняшние дни.
  3. ViPeR — робот, называемый разработчиками разведчиком. Он имеет сравнительно небольшой размер. Высота робота достигает 23 сантиметра. Он оборудован видеокамерой, данные с которой отправляется непосредственно в центр управления.
  4. В популярной онлайн-игре DOTA 2 присутствует персонаж Viper.
  5. Viper — гадюка с английского языка. Самый интересный вариант слова «вайпер».

Вейпер

Термин «вейпер» произошло от «vapour», что в переводе с английского языка означает «пар». Именно поэтому людей, которые увлекаются электронными сигаретами, необходимо называть вейперами.

Культура и развитие

Вейперы сегодня представляют собой модное течение среди молодежи. Vape пользуется огромным спросом в России, Европе и США. Эта субкультура весьма популярна за счёт того, что выступает за самовыражение человека.

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

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

Быть вейпером — что это такое? Члены субкультуры придерживаются мнения, что необходимо отстаивать свою индивидуальность, не вредя себе и окружающим. Вейперы заботятся о здоровье людей и планеты в целом.

Viper | Valorant вики | Fandom

Viper

Настоящее имя:

Сабина

Страна:

США

Очки для ульты:

7

Озвучка:

Мария Фортунатова

«Нельзя задержать дыхание навсегда.»

— Фраза при пике.

Viper (кодовое имя Пандемия) — американский контрактный агент-специалист из игры «Valorant».

Описание

Химик из США Viper устанавливает контроль над полем боя с помощью источающих токсичный газ устройств и ограничивает обзор противникам. А тех, кого ей не удастся отравить, она непременно перехитрит.

— Описание с официального сайта

Способности

Тип: Покупная

Стоимость: 100

Клавиша: C

Описание: Выбирает капсулу с кислотой. Нажмите кнопку ОГОНЬ, чтобы запустить капсулу, которая разбивается при падении, оставляя после себя лужу кислоты. Попавшие в кислоту противники получают урон и замедляются.

Тип: Покупная

Стоимость: 200

Клавиша: Q

Описание: Выбирает распылитель газа. Нажмите кнопку ОГОНЬ, чтобы выбросить распылитель, который остается на месте до конца раунда. Примените умение повторно, чтобы выпустить из распылителя облако ядовитого дыма, расходуя топливо. Можно использовать больше одного раза. Распылитель можно переместить.

Тип: Сигнатурная

Стоимость: Бесплатно

Клавиша: E

Описание: Выбирает распылители газа. Нажмите кнопку ОГОНЬ, чтобы выстрелить длинной очередью распылителей. При повторном использовании распылители образуют стену ядовитого дыма, расходуя топливо. Умение можно применять больше одного раза.

Тип: Ульта

Стоимость: 7 очков

Клавиша: X

Описание: Выбирает распылитель химикатов. Нажмите кнопку ОГОНЬ, чтобы распылить вокруг Viper облако химикатов, сокращая дальность зрения и максимальный запас здоровья находящихся в нем игроков.

Контракты

Контракты всех агентов можно посмотреть здесь.

Фразы

Фразы Viper можно посмотреть здесь.

Галерея

История изменений

Патч 1.01
  • Улучшена производительность особых элементов интерфейса Viper.
    Нам удалось устранить ряд проблем, которые вызывали падение частоты кадров в бою.
Патч 1.00
  • Оптимизированы спецэффекты персонажа.
    +FPS для слабых и средних ПК: улучшения контента должны помочь ПК низкой и средней мощности обрабатывать внешний вид мира.
  • Исправлен эксплойт, из-за которого Nvidia Inspector позволял смотреть сквозь стены, создаваемые умениями Viper.
  • Исправлена ошибка, из-за которой «Токсичная завеса» позволяла игрокам оседлать волну, запуская их вперед на огромной скорости.
  • Исправлена ошибка, позволявшая активировать «Ядовитое облако» в воздухе, если оно было предварительно подобрано.
  • Из описания «Змеиного укуса» убрана информация о замедлении.
Патч 0.50
  • Высота, необходимая для выхода из области действия блокирующих зоны умений, увеличена с 80 до 120.
  • «Змеиный укус» теперь быстрее наносит урон (но урон в секунду остается без изменений).
    Мы знаем, что есть много любителей попрыгать, которые научились проходить через «Зажигательную гранату», «Огненный шар» и «Змеиный укус», не получая при этом урона. И хотя мы не хотим свести на нет значимость этого навыка, мы не можем позволить целому ряду умений стать бесполезными.
  • Радиус действия «Змеиного укуса» увеличен с 350 до 450.
    Мы увеличили радиус «Змеиного укуса», и теперь он соответствует другим умениям, использующимся для блокирования территорий.
  • «Ядовитое облако» больше не начинает восстанавливаться после его получения во время фазы покупок.
Патч 0.49
  • Обновлен внешний вид «Токсичной завесы» Viper на мини-карте — теперь на ней отображаются прорехи в завесе.
  • Исправлена ошибка, из-за которой Viper могла отменить анимацию «Гнезда гадюки».

У суперкара Dodge Viper обнаружен опасный модуль — ДРАЙВ

Проблема выявлена у Вайпера третьего поколения ZB I (2003–2007) с атмосферником V10 8.3, который при помощи шестиступенчатой «механики» выдавал на родстере 507 л.с., 712 Н•м, а у купе — 517 л.с., 725 Н•м. На нашей памяти Viper отзывался в России один раз в 2016-м из-за пружины в сцеплении.

Управляющий модуль удерживающих систем ORC (Occupant Restraint Control module) может беспричинно привести в действие фронтальные подушки безопасности и/или преднатяжители ремней. Индикатор либо предупредит водителя об этом, либо нет. Неожиданная активация эйрбэгов во время движения (удар в лицо) способна нанести травму и/или стать причиной аварии, поэтому модуль ORC следует заменить, хотя несчастных случаев не зафиксировано. В России эту процедуру должны пройти четыре двухдверки Dodge Viper, произведённые с января по апрель 2006 года. Перечень VIN-номеров прилагается.

Ориентировочное время ремонта, по подсчётам FCA, — два часа. Мастерам придётся повозиться, поскольку модуль ORC, изготовленный ZF, «зарыт» глубоко. Нужно отсоединить аккумулятор, разобрать переднюю панель, приборку, рулевую колонку, центральную консоль и так далее.

В Америке аналогичная акция была объявлена в декабре 2019 года и охватила 3329 Вайперов, выпущенных с 18 мая 2004-го по 28 августа 2006-го. Этот период стал «подозрительным», после того как в 2015-м FCA отозвала и проверила машины 2003–2004 годов, но вскоре получила сообщение о внезапной активации эйрбэгов на спорткаре образца 2005-го (обошлось без происшествий). Выяснилось, что виной всему электрическое перенапряжение в одной из двух интегральных схем, управляющих пиропатронами. Компания уверяет: автомобили, произведённые после 2006 года, не имеют данного дефекта.

Комбинированные ViewController’ы и VIPER

Здравствуйте, дорогие друзья! С вами на связи компания IT-Machine.

В одном из проектов, в котором мы работали над iOS-приложением, перед нами стояла задача сделать экран, содержащий несколько вкладок, причём сделать это так, чтобы при добавлении и удалении новых вкладок на экран нам не приходилось перелопачивать весь код. Этот опыт оказался довольно интересным для нас, так как мы отказались от «традиционного» подхода с использованием шаблона проектирования MVC в пользу VIPER. И сегодня нам бы хотелось поделиться этим опытом с вами.

Мы не будем вас здесь в мельчайших подробностях «грузить» тем, что такое VIPER и с чем его едят (об этом вы можете прочитать здесь), а так же рассказывать о том, как реализовать функционал того или иного экрана – этот творческий процесс мы оставим полностью на откуп вам, но мы постараемся доступным языком рассказать о том, каким образом структурировать ваше приложение, сделав каждый его экран (модуль) максимально независимым.

Ну что ж, приступим!

Пусть наш экран имеет подобную структуру:

Header View содержит названия вкладок, которые будут лежать в Body. При нажатии на название вкладки в Header’e соответственно будут меняться экраны в Body.

Итак, само по себе слово VIPER является бэкронимом для View, Interactor, Presenter, Entity и Routing. Соответственно, наш модуль будет состоять из следующих классов:

View – отображает то, что «говорит» Presenter; Interactor – бизнес-логика приложения; Presenter – запрашивает данные от Interactor’а и подготавливает к показу во View; Entity – сущности, которыми управляет Interactor; Router – навигация между модулями.

Выглядит это все следующим образом:

Для того, чтобы настроить «общение» между элементами нашего модуля, необходимо реализовать input- и output-протоколы для View и Interactor’а, которые для удобства лучше будет вынести в отдельные заголовочные файлы.

View-протоколы (общение View и Presenter’а):

@protocol ViewInterfaceOutputView

@end

@protocol ViewInterfaceInputPresenter

@end

Interector-протоколы (общение Interactor’a и Presenter’a):

@protocol InteractorInterfaceInput

@end

@protocol InteractorInterfaceOutput

@end

Чтобы было удобнее работать и сделать наши вкладки максимально независимыми, разобьем наш модуль на несколько сабмодулей, которые будут отображаться в Body. Подобный подход позволит в дальнейшим, при необходимости, добавлять новые или же убирать ненужные экраны с минимальными затратами сил и времени. Header так же необходимо сделать отдельным сабмодулем. По своей сути и структуре сабмодули ничем не будут отличаться от нашего главного модуля, поэтому смело можете реализовывать в них те же самые классы и протоколы!

Немаловажным компонентом здесь так же является протокол DelegateInterface, благодаря которому мы сможем «управлять» нашими сабмодулями в главном модуле.

@protocol DelegateInterface

@end

Теперь, когда мы разобрались, как нам сконфигурировать наш модуль, поговорим о том, каким же образом происходит навигация внутри этого самого модуля. Ответ — очень просто! При нажатии на какую-либо из вкладок в Header’e, View тут же посылает сигнал в Presenter, где вызывается необходимый метод из DelegateInterface (этот метод мы реализуем в Presenter’e нашего модуля, если угодно — в главном Presenter’e) — там мы «говорим» Router’у, что в Body необходимо показать другой сабмодуль. Далее процедура стандартная для каждого модуля/сабмодуля: View спрашивает Presenter, что и как ей показывать, Presenter запрашивает данные у Interactor’a, подготавливает их к показу и дает ответ View. Вот, в общем-то, и все хитрости.

Упрощенно на схеме это можно представить следующим образом:

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

Ссылка на демо-проект, в котором вы можете посмотреть, как описанное выше выглядит на практике: https://github.com/it-machine/Modeles-Lesson

Благодарим каждого из вас за внимание и надеемся, что для кого-то этот рассказ был полезен и стал отправной точкой для покорения новых высот в разработке под iOS.

Велосипед горный Larsen Viper 26″ черный

ТипХардтейл
ПолUnisex
Цветчерный
Размер колес26″
Количество скоростей18
Материал рамыСталь HI-TEN
Передний переключательShimano Tourney (FD-TZ30)
Задний переключательShimano Tourney (RD-TZ20)
Втулка передняяShunfeng SF-HB11FQR
Втулка задняяShunfeng SF-HB11FQR
ШифтерыПравый: Shimano SL-TX30, TOURNEY
Левый: Shimano SL-TX30, TOURNEY
Тип тормозовV — образные
ТормозаWinZip
ВилкаMODE MD-711B
Ход вилки50 мм
РезинаWanda P130(A) 26×2,125
ОбодаАлюминий 6061-t6, черные
Дополнительное оборудованиеОтражатели, подножка, пластиковые крылья
Система шатуновGolden Swallow 42-32-22, шатуны 170 мм, сталь
ВыносИнтегрированый с рулевой колонкой, сталь
Максимальный вес пользователя80 кг
БрендLarsen
Страна брендаФинляндия
Страна производительРоссия
Гарантия Рама 1 год, навесное оборудование 6 месяцев.

Viper | змея | Britannica

Viper , (семейство Viperidae), любой из более чем 200 видов ядовитых змей, принадлежащих к двум группам: ямовые гадюки (подсемейство Crotalinae) и гадюки Старого Света (подсемейство Viperinae), которые некоторыми авторитетами считаются отдельными семьями. Они едят мелких животных и охотятся, поражая и отравляя свою добычу. Змеи характеризуются парой длинных полых клыков, вводящих яд, прикрепленных к подвижным костям верхней челюсти (верхнечелюстной кости), которые загибаются во рту, когда они не используются.Их глаза имеют вертикальные зрачки, а чешуя килеватая. Длина гадюк варьируется от менее 25 см (10 дюймов) у карликовой гадюки Намаква ( Bitis schneideri ) на юге Африки до более 3 метров (10 футов) у бушмастера ( Lachesis muta ) в бассейне Амазонки и Центральная Америка.

Европейская гадюка

Европейская гадюка, или гадюка обыкновенная ( Vipera berus ).

© Hansderzweite / Fotolia

Британская викторина

Змеи, кобры и удавы… О Боже!

Какая самая длинная ядовитая змея в мире? На каком континенте обитают гремучие змеи? Узнайте больше об этих чудесах без конечностей в этой викторине.

Гадюки обитают от пустыни до тропических лесов, в основном в Новом Свете. Эта группа включает в себя, среди прочего, медноголовых, гремучих змей и ферделансов (роды Bothrops и Trimeresurus ). Они могут быть наземными или древесными. Некоторые из них, например мокасины (род Agkistrodon ), являются водными.Все ямочные гадюки, за исключением кустарника-яйцекладчика, живородящие (живородящие).

Прыгающая гадюка ( Bothrops nummifera ).

Дейд Торнтон — Коллекция Национального Одубонского общества / фотоисследователи

Ямчатые гадюки отличаются чувствительными к температуре ямками, расположенными на каждой стороне головы на полпути между каждой ноздрей и глазом. Эта структура чувствительна к инфракрасному излучению, что позволяет змее «видеть» тепловые изображения теплокровной добычи.В паре они обеспечивают бинокулярное зрение, которое помогает змее точно нацелить свой удар на теплокровную добычу. По крайней мере, у некоторых гадюк Старого Света есть инфракрасные рецепторы в той же области, что и ямочные органы, хотя внешних свидетельств о них нет. У некоторых удавов и питонов похожие инфракрасные органы расположены в ямках между чешуей губ.

Змеи Старого Света обитают в пустынях и лесах Европы, Азии и Африки. Обычно они медлительны, коренасты и широкоголовые. Многие из них, например, гадюка обыкновенная ( Vipera berus ) и гадюка-габиан ( Bitis gabonica ), являются наземными.Напротив, древесные гадюки (род Atheris ), такие как рогатая гадюка Матильды ( A. matildae ) из Танзании, тонкие, цепкохвостые и древесные. Некоторые виды откладывают яйца; другие производят живую молодь.

Британская энциклопедия, Inc. Получите подписку Britannica Premium и получите доступ к эксклюзивному контенту. Подпишитесь сейчас

Почему VIPER — плохой выбор для вашего следующего приложения | Сергей Петров

В прошлом году вокруг VIPER было много шумихи, и все очень этим вдохновлены.Большинство этих статей предвзято относятся к этому и пытаются продемонстрировать, насколько это круто. Это не. У него как минимум такое же количество проблем, если не больше, чем у других архитектурных паттернов. В этой статье я хочу объяснить, почему VIPER не так хорош, как его рекламируют, и не подходит для большинства ваших приложений.

В некоторых статьях о сравнении архитектур обычно утверждается, что VIPER полностью отличается от любых архитектур на основе MVC. Это утверждение не соответствует действительности: это просто обычный MVC, где мы разделяем контроллер на две части: интерактор и презентатор.Вид остается прежним, но модель переименовывается в сущность. Маршрутизатор заслуживает некоторых специальных слов: Да, это правда, другие архитектуры не продвигают эту часть в своих сокращениях, но она все еще существует, неявно (да, когда вы вызываете pushViewController , вы пишете простой маршрутизатор) или более явным способом. (например, FlowCoordinators).

Теперь я хочу поговорить о преимуществах, которые предлагает VIPER. Я буду использовать эту книгу как исчерпывающий справочник по VIPER. Начнем со второй цели, соответствующей SRP (принцип единой ответственности).Это немного грубо, но какой странный ум может назвать это выгодой? Вам платят за решение задач, а не за то, что вы прислушиваетесь к какому-то модному словечку. Да, вы все еще используете TDD, BDD, модульные тесты, Realm или SQLite, внедрение зависимостей и все, что вы можете вспомнить, но вы используете это для решения проблемы клиента, а не просто для использования.

Другая цель куда интереснее. Возможность тестирования — очень важная проблема. Он заслуживает отдельной статьи, так как многие люди говорят об этом, но лишь немногие действительно тестируют свои приложения, и еще меньше делают это правильно.

Одна из основных причин этого — отсутствие хороших примеров. Есть много статей о создании модульного теста с assert 2 + 2 == 4 , но нет реальных примеров (кстати, Artsy отлично справляется с открытыми исходными кодами своих приложений, вам стоит взглянуть на их проекты).

VIPER предлагает разделить всю логику на несколько небольших классов с разделенными обязанностями. Это может упростить тестирование , но не всегда. Да, написать unit-test для простого класса несложно, но большинство этих тестов ничего не тестируют.Например, давайте посмотрим на большинство методов докладчика, они просто прокси между представлением и другими компонентами. Вы можете писать тесты для этого прокси, это увеличит тестовое покрытие вашего кода, но эти тесты бесполезны. У вас также есть побочный эффект: вы должны обновлять эти бесполезные тесты после каждого редактирования в основном коде.

Правильный подход к тестированию должен включать тестирование интерактора и презентатора одновременно, потому что эти две части глубоко интегрированы. Более того, поскольку мы разделяем два класса, нам нужно гораздо больше тестов по сравнению со случаем, когда у нас есть только один класс.Это простая комбинация: класс A имеет 4 возможных состояния, класс B имеет 6 возможных состояний, поэтому комбинация A и B имеет 20 состояний, и вы должны проверить все это.

Правильный подход к упрощению тестирования — это привнести в код чистоту, а не просто разделить сложное состояние на несколько классов.

Это может показаться странным, но тестировать представления проще, чем тестировать какой-то бизнес-код. Представление представляет состояние как набор свойств и наследует внешний вид от этих свойств.Затем вы можете использовать FBSnapshotTestCase для сопоставления состояния с внешним видом. Он по-прежнему не обрабатывает некоторые крайние случаи, такие как пользовательские переходы, но как часто вы его реализуете?

VIPER — это то, что происходит, когда бывшие корпоративные программисты Java вторгаются в мир iOS. –N0damage, комментарий reddit

Честно говоря, может ли кто-нибудь взглянуть на это и сказать: «Да, эти дополнительные классы и протоколы действительно улучшили мою способность понимать, что происходит с моим приложением».

Представьте себе простую задачу: у нас есть кнопка, запускающая обновление с сервера, и представление с данными из ответа сервера.Угадайте, сколько классов / протоколов затронет это изменение? Да, для этой простой функции будут изменены как минимум 3 класса и 4 протокола. Разве никто не помнит, как Spring начинался с каких-то абстракций и закончился AbstractSingletonProxyFactoryBean ? Мне всегда нужен «удобный суперкласс bean-компонента прокси-фабрики для компонентов прокси-фабрики, которые создают только синглтоны» в моем коде.

Избыточные компоненты

Как я уже упоминал ранее, презентатор — это обычно очень тупой класс, который просто передает вызовы от представления к интерактору (что-то вроде этого).Да, иногда он содержит сложную логику, но в большинстве случаев это просто избыточный компонент.

«DI-friendly» количество протоколов

Существует обычная путаница с этой аббревиатурой: VIPER реализует принципы SOLID, где DI означает «зависимость , инверсия », а не « инъекция ». Внедрение зависимостей — это частный случай шаблона инверсии управления, который связан, но отличается от инверсии зависимостей.

Инверсия зависимостей — это разделение модулей с разных уровней путем введения между ними абстракций.Например, модуль пользовательского интерфейса не должен напрямую зависеть от сети или модуля постоянства. Инверсия управления отличается, это когда модуль (обычно из библиотеки, которую мы не можем изменить) делегирует что-то другому модулю, который обычно предоставляется первому модулю в качестве зависимости. Да, когда вы реализуете источник данных для своего UITableView , вы используете принцип IoC. Использование одних и тех же языковых функций для разных целей высокого уровня здесь вызывает путаницу.

Вернемся к VIPER.Между каждым классом внутри модуля существует множество протоколов (не менее 5). И они совсем не обязательны. Presenter и Interactor — это не модули из разных слоев. Применение принципа IoC может иметь смысл, но задайте себе вопрос: как часто вы реализуете как минимум двух презентаторов для одного представления? Я считаю, что большинство из вас ответили ноль. Так почему же нужно создать эту группу протоколов, которые мы никогда не будем использовать?

Кроме того, из-за этих протоколов вы не можете легко перемещаться по коду с помощью IDE, потому что cmd + click переместит вас к протоколу, а не к реальной реализации.

Проблемы с производительностью

Это очень важная вещь, и многие люди просто не заботятся об этом или просто недооценивают влияние неверных архитектурных решений.

Я не буду говорить о фреймворке Typhoon (который очень популярен для внедрения зависимостей в мире ObjC). Конечно, это влияет на производительность, особенно когда вы используете автоматическое внедрение, но VIPER не требует, чтобы вы его использовали. Вместо этого я хочу поговорить о среде выполнения и запуске приложений, а также о том, как VIPER замедляет ваше приложение буквально повсюду.

Время запуска приложения. Это редко обсуждается, но это важная тема. Если ваше приложение запускается очень медленно, пользователи будут его избегать. В прошлом году на последнем WWDC была сессия об оптимизации времени запуска приложений. TL; ДР: Время запуска вашего приложения напрямую зависит от того, сколько у вас классов. Если у вас 100 занятий — это нормально, этого никто не заметит. Однако если в вашем приложении всего 100 классов, действительно ли вам нужна эта сложная архитектура? Но если ваше приложение огромно, например, вы работаете над приложением Facebook (18k классов), это влияние огромно, примерно около 1 секунды в соответствии с ранее упомянутым сеансом.Да, холодный запуск вашего приложения займет 1 секунду только для загрузки метаданных всех классов и ничего больше, вы правильно прочитали.

Оперативная диспетчеризация. Это более сложно, гораздо труднее профилировать и в основном применимо только для компилятора Swift (поскольку ObjC имеет широкие возможности времени выполнения и компилятор не может безопасно выполнять эти оптимизации). Давайте поговорим о том, что происходит под капотом, когда вы вызываете какой-либо метод (я использую «вызов» вместо «отправить сообщение», потому что второй термин не всегда подходит для Swift).В Swift есть 3 типа диспетчеризации (от более быстрой к медленной): статическая, табличная отправка и отправка сообщения. Последний — единственный тип, который используется в ObjC, и он используется в Swift, когда требуется взаимодействие с кодом ObjC или когда мы объявляем метод как dynamic . Конечно, эта часть среды выполнения сильно оптимизирована и написана на ассемблере для всех платформ. Но что, если мы сможем избежать этих накладных расходов, потому что компилятор знает, что будет вызвано во время компиляции? Это именно то, что делает компилятор Swift со статической и табличной отправкой.Статическая отправка выполняется быстро, но компилятор не может использовать ее без 100% уверенности в типах в выражении. Но когда тип нашей переменной является протоколом, компилятор вынужден использовать отправку через таблицы-свидетели протокола. Это не буквально медленно, но 1 мс здесь, 1 мс там, и теперь общее время выполнения более чем на одну секунду больше, чем мы могли бы достичь с помощью чистого кода Swift. Этот абзац связан с предыдущим о протоколах, но я думаю, что лучше разделить опасения по поводу просто безрассудного количества неиспользуемых протоколов с реальным вмешательством в компилятор.

Слабое разделение абстракций

Должен быть один — и желательно только один — очевидный способ сделать это.

Один из самых популярных вопросов в сообществе VIPER — «где мне поставить X?». Итак, с одной стороны, есть много правил, как мы должны делать все правильно, но с другой стороны, многие вещи основаны на мнениях. Это могут быть сложные случаи, например обработка CoreData с помощью NSFetchedResultsController или UIWebView . Но даже общие случаи, такие как использование UIAlertController , являются темой для обсуждения.Давайте посмотрим, здесь у нас есть маршрутизатор, работающий с предупреждениями, но там у нас есть представление, представляющее предупреждение. Вы можете попытаться победить это с помощью аргумента, что простое предупреждение является особым случаем простого предупреждения без каких-либо действий, кроме закрытия.

Особых случаев недостаточно, чтобы нарушать правила.

Да, но почему здесь у нас фабрика для создания таких предупреждений? Так что даже в таком простом случае с UIAlertController получилась путаница.Ты хочешь это?

Генерация кода

Считывается значение читаемости.

Как это может быть проблемой с архитектурой? Он просто генерирует кучу классов шаблонов вместо того, чтобы писать их сам. В чем проблема? Проблема в том, что чаще всего вы читаете код (если только вы не работаете в аутсорсинговой компании), а не пишете его. Итак, вы читаете шаблонный код, перепутанный с реальным кодом, большую часть времени. Это хорошо? Я так не думаю.

Я вовсе не преследую цель отговорить вас от использования VIPER. Все еще могут быть некоторые приложения, которые могут принести пользу всем. Однако, прежде чем приступить к разработке своего приложения, вы должны задать себе несколько вопросов:

  1. Будет ли у этого приложения долгий срок службы?
  2. Достаточно ли стабильны спецификации? В качестве альтернативы вы можете закончить бесконечным огромным рефакторингом даже для небольших изменений.
  3. Вы действительно тестируете свои приложения? Будь честен с собой.

Только если вы ответите «да» на все вопросы, VIPER может быть хорошим выбором для вашего приложения.

Наконец, последнее: вы должны использовать свой собственный мозг для своих решений, а не просто слепо доверять парню из Medium или конференции, на которой вы присутствовали, который говорит: «Используйте X, X — это круто». Эти парни тоже могут ошибаться.

Преимущества архитектуры Viper для приложений iOS

Это хорошо известный факт, что программная архитектура в индустрии программного обеспечения имеет решающее значение. Важно разработать код таким образом, чтобы каждый фрагмент можно было легко идентифицировать, имел конкретную цель и логически сочетался с другими частями.Он должен быть легко обслуживаемым, масштабируемым и иметь высокое качество. При разработке приложения для iOS важно подумать о том, какую архитектуру проекта iOS следует использовать. Большинство разработчиков используют схему, предложенную Apple. Однако есть и другие! В этой статье мы рассмотрим архитектуру VIPER , одну из популярных альтернатив MVC, которая может помочь вам преодолеть ее ограничения, сохраняя при этом хорошо организованный код и улучшая процесс разработки.

Что такое архитектура Viper?

VIPER — это бэкроним для View, Interactor, Presenter, Entity и Router.Эта архитектура основана на принципе единой ответственности, который ведет к чистой архитектуре, чтобы иметь лучшую структуру для вашего проекта iOS.

Давайте посмотрим, что означает каждая буква, подробнее:

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

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

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

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

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

В архитектуре Viper каждый блок соответствует объекту с определенными задачами, входами и выходами. Это очень похоже на рабочих на сборочной линии: как только рабочий завершает свою работу над объектом, этот объект передается следующему работнику, пока продукт не будет готов.
Связи между блоками представляют отношения между объектами и тип информации, которую они передают друг другу.Связь от одного объекта к другому осуществляется через протоколы.
Идея этого архитектурного шаблона состоит в том, чтобы изолировать зависимости вашего приложения, сбалансировав делегирование ответственности между сущностями. По сути, архитектура Viper делит логику вашего приложения на более мелкие функциональные уровни, каждый из которых несет строгую заранее определенную ответственность. Это упрощает тестирование взаимодействий на границах между слоями. Он очень хорошо подходит для модульного тестирования и делает ваш код более пригодным для повторного использования.

Ключевые преимущества Viper Architecture

  • Упрощает сложные проекты. Поскольку модули независимы, Viper действительно хорош для больших команд.
  • Делает масштабируемым. Дайте возможность разработчикам одновременно работать над ним максимально эффективно
  • Разъединяет код для повторного использования и возможности тестирования
  • Делит компоненты приложения в зависимости от их роли
  • Устанавливает четкие обязанности, Viper — чемпион по распределению обязанностей
  • Облегчает добавление новых функций
  • Упрощает написание автоматических тестов, поскольку логика пользовательского интерфейса отделена от бизнес-логики.
  • Он поощряет разделение задач, что упрощает внедрение TDD.Interactor содержит чистую логику, не зависящую от какого-либо пользовательского интерфейса, что упрощает работу с тестами
  • Облегчает использование
  • Создает понятные и четко определенные интерфейсы, независимые от других модулей. Это значительно упрощает изменение способа представления различных модулей в вашем интерфейсе пользователю.
  • Облегчает отслеживание проблем с помощью отчетов о сбоях благодаря принципу единой ответственности
  • Делает исходный код более компактным и многоразовым
  • Уменьшает количество конфликтов внутри команды разработчиков
  • Применяет принципы SOLID
  • Уменьшено количество конфликтов слияния
  • Вы можете сначала создать начальный каркас архитектуры, а затем один за другим передать модули другим разработчикам для реализации логики.
  • Делает кодовую базу похожей. Становится намного быстрее читать чужой код.

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

Что касается возможности использования архитектуры Viper в вашем существующем приложении, в этом сценарии рассмотрите возможность создания новой функции с помощью Viper. Это позволяет вам создавать модуль с использованием архитектуры Viper, а также помогает выявлять любые существующие проблемы, которые могут затруднить принятие архитектуры, основанной на принципе единой ответственности.

Архитектура

Viper — хорошее решение для создания работающего программного обеспечения, приложение для iOS, которым можно гордиться!
Если у вас есть какие-либо вопросы, не стесняйтесь задавать их в разделе комментариев ниже!

И не забудьте подписаться на нашу ежемесячную новостную рассылку, чтобы получать советы и тенденции в мире программного обеспечения!

Хотите обсудить возможности для вашей компании ? Свяжитесь с нами, мы будем рады обсудить ваш проект!

Если вам эта статья показалась интересной, вам может понравиться…

Хорошего дня!

Что такое VIPER?

Это первая публикация из серии статей об архитектуре проекта iOS.В этих сообщениях я не собираюсь вдаваться в подробности о плюсах и минусах архитектурных паттернов, вместо этого основное внимание будет уделено описанию различных архитектурных структур, которые могут быть применены в проекте iOS. Однако я намерен написать более самоуверенную статью в качестве резюме / заключения для различных концепций, представленных в этой серии позже.

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

VIPER обозначает различные компоненты архитектурного шаблона; View , Interactor , Presenter , Entity и Routing . Основной принцип VIPER — разделить функциональность приложения на эти разные компоненты, чтобы добиться низкой связи между различными компонентами приложения и следовать принципу единой ответственности.

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

Различные компоненты VIPER

Для начала я рассмотрю различные компоненты VIPER и их обязанности. При описании различных компонентов VIPER я приведу примеры того, где можно найти различные компоненты в традиционно настроенном проекте MVC iOS (где это применимо).

После того, как компоненты будут представлены, я сосредоточусь на том, как различные компоненты объединяются в проект и как компоненты соотносятся друг с другом.

Посмотреть

Представление отвечает за отображение информации для пользователя приложения. Поскольку представление — это то, что видит пользователь, это важный компонент, который предоставляет пользователю прямой интерфейс к приложению — в традиционном проекте iOS это может быть представление в раскадровке или в файле .xib. Таким образом, представление также отвечает за предоставление приложению возможности для ввода информации пользователем.

Interactor

В интеракторе содержится бизнес-логика приложения для обработки данных.Интерактор не несет прямой ответственности за получение данных с сервера, он часто назначается одному или нескольким Network Manager . Однако интерактор отвечает за взаимодействие с этими Сетевыми менеджерами . Поскольку конкретному приложению может потребоваться получение данных из нескольких разных источников данных, интерактор может инициализировать сетевые запросы через несколько разных сетевых менеджеров. Интерактор должен получить необходимые данные для конкретного варианта использования приложения из разных точек данных в соответствии с бизнес-логикой приложения.После получения данных со всех внешних серверов через менеджеров сети, интерактор обрабатывает любые манипуляции с данными, которые могут быть необходимы.

Примечание: Если вы знакомы с современной архитектурой MVVM, ответственность за взаимодействие можно сравнить с ответственностью ViewModel MVVM.

Ведущий

Ответственность ведущего заключается в применении логики отображения контента. Ведущий использует данные, предоставленные интерактором, а затем применяет необходимую логику того, как эти данные должны быть представлены в представлении.Докладчик также отвечает за логику того, как приложение должно реагировать на вводимые пользователем данные и действия пользователя. Эта ответственность часто возлагается на ViewController в контексте проекта iOS.

Организация

Сущность — это модель хранения данных. Это может быть модель постоянного хранения, такая как объект Core Data , или более временная модель хранения, такая как Struct , Class или Enum .Предполагается, что объект не содержит никакой логики, связанной с форматированием данных перед их отображением, а также не должен содержать логику о том, как получить данные (например, сетевой запрос). Сущность просто содержит переменные, специфичные для модели, которую она представляет. Вместо этого ответственность за отображение данных объекта возлагается на докладчика, а администратор сети отвечает за получение данных.

Маршрутизация

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

Примечание: Компонент маршрутизации берет на себя ответственность за навигацию между различными представлениями, это ответственность, которая традиционно разделена между переходом раскадровки и ViewController (в функции prepareForSegue).Поэтому приложение, использующее архитектуру VIPER, обычно не содержит переходов между различными представлениями раскадровки.

Известный

Согласно архитектуре VIPER, никакие классы, кроме диспетчера данных, не должны иметь никакой информации о Core Data или об объектах Core Data. Вместо этого класс диспетчера данных преобразует объект Core Data в PONSO (обычный старый NSObject) перед передачей его любому другому классу, например классу интерактора.

Использование модулей с VIPER

Экран или набор экранов могут быть добавлены к модулю .Модуль может быть просто папкой в ​​проекте, которая содержит функции, необходимые для определенного экрана. Шаблон модуля может быть полезен для замены или повторного использования определенного поведения для разных платформ. Например, если определенные функции существуют только для приложения для iPad, их легко заменить другим модулем для приложения для iPhone.

Как будет выглядеть эта структура в проекте iOS?

До сих пор в этой статье я определил различные компоненты VIPER, а также различные обязанности этих компонентов.Давайте рассмотрим, как эти компоненты взаимодействуют друг с другом и как они сочетаются друг с другом в проекте более подробно.

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

В то время как View предоставляет пользователю определенный экран приложения, Presenter играет важную роль в принятии решений о том, как данные должны отображаться в представлении и что делать с данными, которые пользователь вводит. вид. Докладчик получает данные, которые должны отображаться в конкретном представлении, из Interactor и форматирует данные перед их отображением в представлении. Когда пользователь нашего приложения взаимодействует с представлением, докладчик должен решить, как следует изменить представление, чтобы предоставить пользователю необходимую обратную связь.

Если вводимые пользователем данные необходимо сохранить для последующего извлечения, докладчик пересылает данные в интерактор. Интерактор решает, как данные должны быть отформатированы, применяя соответствующую бизнес-логику, перед тем, как передать их сетевому администратору (для внешнего хранения информации) или Entity .

Приложение часто содержит несколько экранов с разными функциями, предназначенных для выполнения конкретного варианта использования. Каждый из этих экранов (или набора связанных экранов) состоит из View , Interactor , Presenter , Entity и компонента Routing , которые вместе можно назвать Модуль .Поскольку для некоторых экранов могут потребоваться те же объекты или тот же взаимодействующий элемент, что и для другого экрана, компонент VIPER может повторно использоваться в разных модулях.

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

Полезные ссылки:

Использование архитектуры VIPER на Android

Начав как разработчик Android, а затем работая с iOS, я имел контакт с несколькими архитектурами различных проектов — некоторыми хорошими, а некоторыми плохими.

Я с удовольствием использовал архитектуру MVP для Android, пока не встретил — и проработал с ней восемь месяцев — архитектуру VIPER в проекте iOS. Когда я вернулся к Android, я решил адаптировать и реализовать на нем VIPER, несмотря на то, что некоторые другие разработчики полагали, что использовать архитектуру iOS на Android не имеет смысла.Учитывая фундаментальное различие между платформами Android и iOS, у меня возникло несколько вопросов о том, насколько полезен VIPER для Android. Будет ли это выполнимо и стоит ли затраченных усилий? Начнем с основ.

Что такое VIPER?

VIPER — это чистая архитектура, которая в основном используется при разработке приложений для iOS. Это помогает сохранить код чистым и организованным, избегая ситуации с массивным представлением-контроллером.

VIPER обозначает V iew I nteractor P resenter E ntity R external, классы, которые имеют четко определенную ответственность в соответствии с принципом единой ответственности.Вы можете прочитать об этом больше в этой отличной статье.

Архитектуры Android

Уже есть несколько очень хороших архитектур для Android. Наиболее известными из них являются Model-View-ViewModel (MVVM) и Model-View-Presenter (MVP).

MVVM имеет большой смысл, если вы используете его вместе с привязкой данных, и поскольку мне не очень нравится идея привязки данных, я всегда использовал MVP для проектов, над которыми работал. Однако по мере роста проектов докладчик может превратиться в огромный класс с множеством методов, что затрудняет поддержку и понимание.Это происходит потому, что он отвечает за множество вещей: он должен обрабатывать события пользовательского интерфейса, логику пользовательского интерфейса, бизнес-логику, сети и запросы к базе данных. Это нарушает принцип единой ответственности, и VIPER может это исправить.

Давайте исправим!

Помня об этих проблемах, я начал новый проект Android и решил использовать MVP + Interactor (или VIPE, если хотите). Это позволило мне переложить ответственность с ведущего на Interactor. Оставление докладчика с обработкой событий пользовательского интерфейса и подготовкой данных, поступающих от Interactor, для отображения в представлении.Тогда Interactor отвечает только за бизнес-логику и выборку данных из БД или API.

Также я начал использовать интерфейсы для связывания модулей между собой. Таким образом, они не смогут получить доступ к другим методам, кроме объявленных в интерфейсе. Это защищает структуру и помогает определить четкую ответственность за каждый модуль, избегая ошибок разработчика, таких как размещение логики в неправильном месте. Вот как выглядят интерфейсы:

 
class LoginContracts {
  interface View {
    fun goToHomeScreen (пользователь: Пользователь)
    весело showError (сообщение: строка)
  }
  
  interface Presenter {
    весело onDestroy ()
    весело onLoginButtonPressed (имя пользователя: String, пароль: String)
  }
  
  interface Interactor {
    забавный логин (имя пользователя: строка, пароль: строка)
  }
  
  interface InteractorOutput {
    fun onLoginSuccess (пользователь: Пользователь)
    забава onLoginError (сообщение: строка)
  }
}
  

А вот код, иллюстрирующий классы, реализующие эти интерфейсы (он на Kotlin, но Java должна быть такой же).

 
class LoginActivity: BaseActivity, LoginContracts.View {
  
  var presenter: LoginContracts.Presenter? = LoginPresenter (это)

  override fun onCreate () {
    // ...
    loginButton.setOnClickListener {onLoginButtonClicked ()}
  }
  
  override fun onDestroy () {
    ведущий? .onDestroy ()
    презентер = ноль
    super.onDestroy ()
  }

  приватное развлечение onLoginButtonClicked () {
    презентатор? .onLoginButtonClicked (usernameEditText.text, passwordEditText.text)
  }
  
  fun goToHomeScreen (пользователь: Пользователь) {
    val intent = Intent (view, HomeActivity :: class.Ява)
    intent.putExtra (Constants.IntentExtras.USER, пользователь)
    startActivity (намерение)
  }
  
  fun showError (message: String) {
    // показывает ошибку в диалоге
  }
}

class LoginPresenter (var view: LoginContracts.View?): LoginContracts.Presenter, LoginContracts.InteractorOutput {
    var intector: LoginContracts.Interactor? = LoginInteractor (это)

    fun onDestroy () {
      view = null
      интерактор = ноль
    }

    fun onLoginButtonPressed (имя пользователя: String, пароль: String) {
      интерактор ?.логин (логин, пароль)
    }

    fun onLoginSuccess (пользователь: Пользователь) {
      view? .goToNextScreen (пользователь)
    }
    
    fun onLoginError (message: String) {
      view? .showError (сообщение)
    }
}

class LoginInteractor (вывод var: LoginContracts.InteractorOutput?): LoginContracts.Interactor {
  забавный логин (имя пользователя: строка, пароль: строка) {
    LoginApiManager.login (имя пользователя, пароль)
                ? .subscribeOn (Schedulers.io ())
                ? .observeOn (AndroidSchedulers.mainThread ())
                ?подписываться({
                          // что-то делает с пользователем, например сохраняет его или токен
                          вывод? .onLoginSuccess (it)
                          },
                        {вывод? .onLoginError (it.message?: "Ошибка!")})
  }
}
  

Полный код доступен в этом Gist.

Вы можете видеть, что модули создаются и связываются вместе при запуске. Когда Activity создается, он инициализирует Presenter, передавая себя как View в конструктор.Затем Presenter инициализирует Interactor, передавая себя как InteractorOutput .

В проекте iOS VIPER это будет обрабатываться маршрутизатором, создавая UIViewController или получая его из раскадровки, а затем соединяя все модули вместе. Но на Android мы не создаем Activity сами: мы должны использовать Intents, и у нас нет доступа к новому Activity из предыдущего. Это помогает предотвратить утечку памяти, но может быть неприятно, если вы просто хотите передать данные в новый модуль.Мы также не можем добавить Presenter к дополнительным функциям Intent, потому что это должен быть Parcelable или Serializable . Это просто невыполнимо.

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

VIPE + Router

Вышеупомянутая реализация VIPE решила большинство проблем MVP, разделив обязанности Presenter с Interactor.

Однако представление не такое пассивное, как представление iOS VIPER.Он должен обрабатывать всю обычную ответственность за просмотр, а также выполнять маршрутизацию к другим модулям. Это НЕ должно входить в его обязанности, и мы можем добиться большего. Войдите в маршрутизатор.

Вот различия между VIPE и VIPER:

 
class LoginContracts {
  interface View {
    весело showError (сообщение: строка)
    // весело goToHomeScreen (user: User) // Это больше не входит в обязанности View
  }

  interface Router {
    fun goToHomeScreen (user: User) // Теперь маршрутизатор обрабатывает это
  }
}

class LoginPresenter (var view: LoginContracts.Посмотреть?): LoginContracts.Presenter, LoginContracts.InteractorOutput {
    // теперь презентатор имеет экземпляр Router и передает ему Activity в конструкторе
    var router: LoginContracts.Router? = LoginRouter (просмотреть как? Активность)
    
    // ...
  
    fun onLoginSuccess (пользователь: Пользователь) {
      маршрутизатор? .goToNextScreen (пользователь)
    }
    
    // ...
}

class LoginRouter (var activity: Activity?): LoginContracts.Router {
  fun goToHomeScreen (пользователь: Пользователь) {
    val intent = Intent (view, HomeActivity :: class.Ява)
    intent.putExtra (Constants.IntentExtras.USER, пользователь)
    активность? .startActivity (намерение)
  }
}
  

Полный код доступен здесь.

Теперь мы переместили логику маршрутизации просмотра в Маршрутизатор. Ему нужен только экземпляр Activity, чтобы он мог вызвать метод startActivity . Он по-прежнему не связывает все вместе, как iOS VIPER, но по крайней мере соблюдает принцип единой ответственности.

Заключение

Разработав проект с помощью MVP + Interactor и помогая коллеге разработать полный проект VIPER Android, я могу с уверенностью сказать, что архитектура действительно работает на Android, и это того стоит.Классы становятся меньше и удобнее в обслуживании. Он также направляет процесс разработки, потому что архитектура дает понять, где должен быть написан код.

Здесь, в Cheesecake Labs, мы планируем использовать VIPER в большинстве новых проектов, чтобы обеспечить лучшую ремонтопригодность и более понятный код. Кроме того, это упрощает переход от проекта iOS к проекту Android и наоборот. Конечно, это развивающаяся адаптация, поэтому здесь нет ничего высеченного в камне. Мы будем рады получить отзывы об этом!

Что такое Viper — Viper — высокопроизводительные вычисления

Viper — это высокопроизводительный компьютер (HPC) Университета Халла

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

Настоящее исследование включает изучение Galaxy , V , колебательных эффектов молекул , S , электронных проводников, эффектов и , компьютерной лингвистики .

Что такое HPC?

HPC — это термин, обозначающий использование нескольких компьютеров параллельно для выполнения больших и сложных задач.Цель этого — иметь возможность разделить такую ​​задачу на более мелкие части и совместно использовать их на других компьютерах в HPC. Вместе с быстрой связью между компьютерами это обеспечивает отличный способ обработки таких задач.

Это обеспечивает очень высокую производительность задач.

Типичные рабочие процессы высокопроизводительных вычислений

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

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

Параллельный (один узел)
Здесь задание будет выполняться на одном узле (т.е. до 28 ядер), вероятно, с использованием OpenMP, OpenMPI или одного из установленных пакетов программного обеспечения.

Параллельный (несколько узлов)
Здесь для задания потребуется больше ядер, чем предусмотрено одним узлом (в настоящее время 28 ядер), опять же, это, вероятно, будет использовать OpenMP, OpenMPI или одно из установленного программного обеспечения.

High Memory
Здесь для выполнения задания требуется большой объем физической памяти (в настоящее время 1 Тбайт). Обычно это очень большой набор данных или обработка, требующая большой модели памяти для хранения промежуточных данных.

GPU
Viper имеет 4 узла GPU, которые можно использовать для быстрых ускоренных вычислений с GPU-ускорением, например, с использованием высокопроизводительных манипуляций с графикой. NVidia CUDA, OpenCL и OpenACC являются примерами программирования внутри этого узла.Пример установленного программного обеспечения — Paraview, Virtual-GL или один из программных пакетов.

Визуализация
Viper имеет 2 узла визуализации, которые можно использовать для интерактивной визуализации и просмотра 3D-моделей. Пример установленного программного обеспечения — Avizo. https://www.fei.com/software/amira-avizo/

Техническая спецификация

Вычислительные узлы
Viper основан на операционной системе Linux и состоит из примерно 5 500 процессорных ядер со следующими специальными областями:
— 180 вычислительных узлов, каждый с 2x 14-ядерными процессорами Broadwell E5-2680v4 (2.4–3,3 ГГц), 128 ГБ DDR4 RAM
— 4 узла с высокой памятью, каждый с 4-мя 10-ядерными процессорами Haswell E5-4620v3 (2,0 ГГц), 1 ТБ DDR4 RAM
— 4 узла графического процессора, каждый из которых идентичен вычислительным узлам с добавлением из 4х графических процессоров Nvidia Tesla K40m на узел
— 2 узла визуализации с 2х Nvidia GTX 980TI
— межсоединение Intel Omni-Path (узел-коммутатор 100 Гбит / с и коммутатор-коммутатор)
— Параллельная файловая система 500 ТБ (BeeGFS)

Инфраструктура
— 4 стойки со специальным охлаждением и защитой от горячих коридоров
— Дополнительная стойка для хранения и управления компонентами
— Выделенный высокоэффективный чиллер на крыше AS3 для охлаждения
— ИБП и аварийное переключение питания генератора

Гадюка | Titanfall Wiki | Фэндом

Гадюка

Причина смерти

Застрелен Джеком Купером

Voodoo 1, Viper на станции.Ваше путешествие заканчивается здесь, пилот. Небеса принадлежат мне. Некуда бежать, негде спрятаться.

— Гадюка

Гадюка является членом Apex Predators и второстепенным антагонистом в Titanfall 2 . Он носит шлем, похожий на шлем пилота Pulse Blade, и использует сильно модифицированное шасси Northstar, трофеи долгой и успешной карьеры наемника. [1] После победы над Вайпером выдается достижение Defanged.

Биография

Битва при Тифоне

Единственная известная роль Змеи в битве при Тифоне — защита и сопровождение Ковчега на аэродром IMC, где находится транспортное судно IMS Draconis . Позже его увидят вместе со Слоном в ангаре корабля перед его аварийным взлетом. [2]

Затем Гадюка взлетит в небо на своем Титане, демонстрируя впечатляющий набор обновлений, позволяющих значительно увеличить время полета и улучшить отслеживание ракет во время борьбы с эскадрильями Militia Crows.В конечном итоге он выпустит залп ракет по «Вдове», в которой находятся Джек Купер и БТ-7274, в результате чего они упадут в небо и приземлятся на десантный корабль, пилотируемый Баркером. [3]

Позывная Viper.

Гадюка продолжала сражаться с военно-воздушными силами ополчения, в конце концов пробравшись к IMS Malta , чтобы помешать BT и Джеку достичь Draconis . Два пилота вступят в ожесточенное сражение на внешнем корпусе линкора, в котором Джек в конечном итоге одержит победу и нанесет критический урон Титану Змеи.После того, как двое верят, что Вайпер мертва, они продолжат свою попытку подняться на борт Draconis , но Вайпер вернется и столкнет их с корабля в небо. Они приземлились на борт корпуса Draconis и вступили в безумную рукопашную схватку. BT взялся бы за Viper, который, в свою очередь, активировал свои двигатели и врезал BT обратно в землю. Тогда Вайпер оторвал бы левую руку БТ, но Авангард выстрелил в его собственный люк. Затем Купер выстрелит в обнаженную гадюку из своей кабины из стрелкового оружия, в результате чего его Титан выпустит залп ракет, которые попадут в Драконис и заставят его потерять высоту.Будучи раненым, Гадюка осталась умирать на разбившемся Draconis . [3]

Общая информация

  • Изначально в этом беспокойном обмене участвовал второй Ронин-Титан, как видно из трейлера кампании Titanfall 2 на E3 2016. Вместо того, чтобы сражаться с Вайпером после того, как он оправился от падения, он должен был сражаться с Ронином Титаном, хотя сцена меняется до того, как что-либо еще было раскрыто. Что стало с этим Ронином, в настоящее время неизвестно. Возможно, этот Ронин был просто заменой Вайпера, чтобы не испортить битву с боссом в трейлере.
  • Viper имеет несколько общих черт с Char Aznable из франшизы Mobile Suit Gundam .
    • Лицо Вайпера скрыто за белым шлемом пилота, так же, как Чар скрывал свое лицо, используя белую маску и шлем во время событий оригинального сериала.
    • Костюм пилота
    • Viper использует ту же цветовую палитру, что и костюм пилота Чара из фильма Контратака Чара , темно-желтый с черными вставками.
    • Оба пилотных отряда красного цвета, которые настроены, прежде всего, на скорость и маневренность.
    • С точки зрения мастерства, оба показаны как пилоты-асы, способные в одиночку уничтожить несколько вражеских кораблей за один бой.
  • Некоторые реплики Змеи созданы под влиянием таких фильмов, как Top Gun и Aliens .
    • Его фраза «Гадюка схватила тебя, 5×5» похожа на строку, сказанную капралом Колетт Ферро в фильме « Пришельцы», , когда корабль UD-4L Cheyenne входит в атмосферу планеты LV-426.
    • Его упоминание «Voodoo one» является ссылкой на Top Gun , как и его позывной.В Top Gun персонажа Гадюку играет Том Скеррит, который также появился в Alien . Гадюка также физически похожа на Скеритта.
    • Viper также ссылается на The Matrix , говоря: «Уклоняйся от этого!»
  • Его шлем использовался для оригинальной женской модели Pulse Blade во время технического теста.
  • Кастомный Northstar
  • Viper был единственным в своем роде, который мог летать в течение продолжительных периодов времени, например, самолет, из-за нестандартных и дорогостоящих модификаций, осуществленных Viper лично. [1] Возможно, что Viper Thrusters являются производными от его позывного (или наоборот).
  • Цитата Змеи «Тебе нужно двигаться немного быстрее, чем этот сын. Скорость — это жизнь ». Скорее всего, это отсылка к поговорке старого авиатора: «Скорость полета — это жизнь, высота — это страхование жизни».
  • На изображении, опубликованном в Твиттере Apex Legends, в магазине модов Rampart можно увидеть то, что похоже на титана Viper класса Northstar, рядом с различными экранами, отображающими чертежи различных компонентов титана, таких как ракетные стойки и двигатели.
    • Дочь Вайпера, Кайри, с помощью Rampart спасла неповрежденное летное ядро ​​с Northstar Viper и построила новый реактивный ранец для использования в играх Apex.

Leave a Reply

Ваш адрес email не будет опубликован. Обязательные поля помечены *

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>