Viper Fit S Signature – Комбоустройство | Автогир Москва

Описание

Viper Combo Fit S Signature – сигнатурное комбо-устройство на магнитном крепление с возможностью подключения задней камеры и функцией WI-FI. В данном гаджете применены технологии неодимовых магнитов со сквозным питанием, благодаря чему устройство надежно крепится к брекету, что исключает падение устройства в вашем автомобиле на неровной поверхности дороги.

Новая технология с применением SIGNATURE-Z и системы ANTI-CAS помогает лучше определить современные сигналы радарных комплексов. Главным преимуществом является определение и сообщение о типе радарного сигнала, а также исключение ложных срабатываний.

GPS информатор с постоянно обновляемой базой камер полицейских радаров помогает избежать непредвиденных расходов. Данную базу полицейский радаров можно обновить с помощью WI-FI приложения напрямую через ваш смартфон.

Возможность подключения дополнительной влагозащищенной камеры заднего вида (в комплект не входит) позволяет осуществлять водителю полный контроль над ситуацией на дороге. Данная камера также является помощником при парковке.

Оптическая система из 6 стеклянных линз, процессор AiT-8339 и высокотехнологичный сенсор Sony-307 обеспечивают реалистичное и четкое видео с максимальным уровнем детализации.

Широкоформатный IPS-дисплей в совокупности с матрицей Sony передает качественное изображение без бликов в любое время суток.

Быстросъёмное крепление на магнитах обеспечивает надёжную фиксацию прибора, а также возможность лёгкого снятия устройства. Питание подаётся на крепление, которое крепится к лобовому стеклу при помощи 3М-скотча.

Удобное приложение Viper X-drive/Viper Fit S позволяет позволяет подключаться к видеорегистратору без проводов, выполнять настройку и получать доступ к видеоматериалам.

Опционально установленный CPL-фильтр значительно улучшит качество видеосъемки в яркую солнечную погоду.

• Точное определение координат GPS/GLONASS.

• Голосовое оповещение о стационарных камерах и радарах.

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

• Патч-антенна с увеличенной дальностью обнаружения.

• Супергетеродинный преобразователь.

• Режим контроля остановки и стоянки.

• Защита от ложных срабатываний.

• Обнаружение сигналов в диапазоне: X, K, Ka, La, Стрелка.

• Обновление базы стационарных камер.

• Применена технология DSP (Digital Signal Processing) обработки сигналов с помощью сигнатурного алгоритма работы фильтрации Z-сигнатур.

• Исключает сигналы от таких источников, как автоматические двери на заправочных станциях, в магазинах и с телекоммуникационных передатчиков.

• Блокирует сигналы CAS (система предупреждения столкновений), установленные на современных автомобилях.

• Способен идентифицировать радар по излучаемому типу сигнала (Крис, Кордон, Стрелка, Искра и т.д.)

• Отображение на мониторе скоростных ограничений.

• Магнитное крепление.

• Установка CPL фильтра опционально.

• Поддержка установки камеры заднего вида (внешняя или внутренняя).

• Автоматический старт записи при подаче питания (зажигания).

• Возможность отображения даты и времени, номера автомобиля.

• Датчик ускорения (G-сенсор) сохраняет файлы от перезаписи (чувствительность датчика можно отрегулировать самостоятельно).

• Запись звука с возможностью отключения.

• Отключение дисплея (ночной режим).

Вступление


Про архитектуру

писали уже достаточно

, в том числе и на хабре (

Поэтому я решил не отставать от других и написать очередное «полезное» руководство.

Все началось с того, что эппловская

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

. Если вкратце, то

MVC

превратился в

Massive View Controller

, т.е. огромный вьюконтроллер, в котором ему позволялось очень много. В нем было много

UI

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

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

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

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

Про сертификаты:  Обязательно ли проходить курсы сомелье, чтобы стать хорошим дегустатором

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

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

Глава 0. схема архитектуры viper

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

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


Каждая буква из аббревиатуры VIPER на ней что-то обознает:

V

iew–

I

nteractor–

P

resenter–

E

ntity–

R

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

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

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.

Про сертификаты:  Возврат ошибочно перечисленных денежных средств

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

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

Viper Fit S Signature - Комбоустройство | Автогир Москва

Как ни странно, но рассматривать архитектуру я начну не с первого более сложного экрана, а с экрана

«О приложении»

, который очень простой. Сам экран имеет пару лейблов, кнопку

«Закрыть»

и кнопку со ссылкой на сайт. При нажатии на

«Закрыть»

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

Такие экраны в приложении не показывают всю мощь и необходимость 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
    }
}

Понятное дело, чтобы не получить

, презентер у вьюконтроллера указывается как

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)
    }
}

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

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

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

Заключение

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

Wireframe

, другие придумывают что-то еще. Кто-то передает во вьюконтроллер

Entity

. Последнее, конечно, неправильно.

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

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

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

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

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

Оцените статью
Мой сертификат
Добавить комментарий