Skip to content

Latest commit

 

History

History
370 lines (270 loc) · 17.5 KB

service_utils.md

File metadata and controls

370 lines (270 loc) · 17.5 KB

Service utils

Содержание

  • StringAttributes - упрощение работы с NSAttributedString
  • BrightSide - позволяет определить наличие root на девайсе.
  • VibrationFeedbackManager - позволяет воспроизвести вибрацию на устройстве.
  • QueryStringBuilder - построение строки с параметрами из словаря
  • RouteMeasurer - вычисление расстояния между двумя координатами
  • SettingsRouter - позволяет выполнить переход в настройки приложения/устройства
  • AdvancedNavigationStackManagement - расширенная версия методов push/pop у UINavigationController
  • WordDeclinationSelector - позволяет получить нужное склонение слова
  • LocalStorage – утилита для сохранения / удаления / загрузки Codable моделей данных в файловую систему
  • GeolocationService – сервис для определения геопозиции пользователя
  • SecurityService - сервис для шифрования и сохранения в keychain/inMemory секретных данных
  • MailSender - утилита для посылки email сообщений (либо через MFMailComposeController, либо через стандартное приложение почты)
  • MoneyModel - структура для работы с деньгами
  • MapRoutingService - сервис для построения маршрутов и отображения точек в сторонних навигационных приложениях

StringAttributes

Утилита для упрощения работы с NSAttributedString

Варианты использования:

  1. Для простых строк можно использовать метод .with(attributes: [StringAttribute])

Пример:

let attrString = "Awesome attributed srting".with(attributes: [.kern(9), lineHeight(20)])
  1. Для строк, где для разных участков текста необходим различный стиль, есть StringBuilder.

Пример:

let globalSttributes: [StringAttribute] = [
    .font(.systemFont(ofSize: 14)),
    .foregroundColor(.black)
]
let attributedString = StringBuilder(globalAttributes: globalSttributes)
    .add(.string("Title"))
    .add(.delimeterWithString(delimeter: .init(type: .space), string: "blue"), 
         with: [.foregroundColor(.blue)])
    .add(.delimeterWithString(delimeter: .init(type: .lineBreak), string: "Base style on new line"))
    .add(.delimeterWithString(delimeter: .init(type: .space), string: "last word with it's own style"), 
         with: [.font(.boldSystemFont(ofSize: 16)), .foregroundColor(.red)])
    .value

Возможные проблемы:

  • при добавлении к StringBuilder только разделителя (.add(.delimeter)) (без указания шрифта как локально, так и в рамках текущего блока) может произойти потеря равнения параграфа по вертикали потому что для разделителя будет использоваться стандартный шрифт системы (лейбла). Для предотвращения данной проблемы желательно использование разделителей в паре с текстом (.add(.delimeterWithString(...)))

BrightSide

Утилита позволяет определить наличие root на устройстве.

Пример:

if BrightSide.isBright() {
    print("Девайс чист как белый лист")
} else {
    print("На девайсе получен root доступ")
}

VibrationFeedbackManager

Утилита для воспроизведения вибраций с поддержкой taptic engine (1.0/2.0). Автоматически определяет тип девайса и вызывает корректный тип вибрации.

Пример:

/// воспроизвести вибрацию по событию error
VibrationFeedbackManager.playVibrationFeedbackBy(event: .error)

QueryStringBuilder

Утилита позволяет построить строку типа "key1=value1&key2=2.15&key3=true", в виде которой обычно представляются параметры GET запроса, из словаря [String: Any].

Пример:

let dict: [String: Any] = ["key1": "value1", "key2": 2.15, "key3": true]
let queryString = dict.toQueryString()

RouteMeasurer

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

Пример:

RouteMeasurer.calculateDistance(between: firstCoordinate, and: secondCoordinate) { (distance) in
    guard let distance = distance else {
        return
    }
    let formattedDistance = RouteMeasurer.formatDistance(distance, meterPattern: "м", kilometrPatter: "км"))
}

SettingsRouter

Утилита для упрощения перехода к настройкам приложения или к конкретному разделу настроек устройства.

Пример:

SettingsRouter.openDeviceSettings()

AdvancedNavigationStackManagement

Данная утилита предоставляет возможность вызова методов push и pop у UINavigationController с последующим вызывом completion-замыкания после завершения действия.

Пример:

navigationController?.pushViewController(controller, animated: true, completion: {
    print("do something else")
})

WordDeclinationSelector

Утилита, позволяющая получить верное склонение слова в зависимости от числа элементов.

Пример:

let correctForm = WordDeclinationSelector.declineWord(for: 6, from: WordDeclensions("день", "дня", "дней"))

LocalStorage

Утилита для сохранения / удаления / загрузки Codable моделей данных в файловую систему.

ВАЖНО: работает на синхронной очереди

Пример:

// Модель должна быть Codable

struct Model: Codable {
    let id: Int
    let name: String
}

let model = Model(id: 2, name: "Ибрагим")

// Сохранение модели с необходимым названием файла

LocalStorage.store(object: model, as: "filename")

// Загрузка модели с указанием имени файла и типом модели

LocalStorage.load(fileName: "filename", as: Model.self)

// Удаление модели с указанием имени файла

LocalStorage.remove(fileName: Constants.newLocalPostFileName)

GeolocationService

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

Пример:

// Создание сервиса:

let service = GeolocationService()

// Получение статуса доступа к сервисам геолокации:

service.requestAuthorization { result in
    switch result {
    case .success:
        // access is allowed
    case .denied:
        // user denied access to geolocation
    case .failure:
        // user doesn't gave permission on his geolocation in the system dialog
    case .requesting:
        // system dialog is currently displayed
    }
}

// Получение геопозиции пользователя:

service.getCurrentLocation { result in
    switch result {
    case .success(let location):
        // do something usefull with user location
    case .denied:
        // user denied access to geolocation
    case .error:
        // some error ocured
    }
}

SecurityService

Сервис, который умеет шифровать и сохранять в keychain/inMemory секретные данные по ключу, например по пину Шифрование происходит по следующему принципу:

  1. Берется SHA3.224 от пина
  2. Генерируется криптостойкой случайное число на 32 бита - соль
  3. Из битового представления соли получаем hex-string
  4. Вставляется пин в соль - получаем ключ
  5. Генерируется вектор инициаллизации - 4 бита
  6. Шифруем наши данные алгоритмом Blowfish токены используя ключ.
  7. Полученный шифротекст сохраняем в кейчейн
  8. Ключ так же сохраняем в кейчейне

PinCryptoBox - отвечает за шифрование/дешифрование и сохранение/загрузку из стореджа, при инициализации принимает SecureStore, HashProvider, SymmetricCryptoService и ключи к соли, вектору инициализации, шифруемым данным и хешу. Реализует протокол CryptoBox

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

HackWrapperCryptoBox - обертка для хак-бокса, используется чтобы на лету подменять шифровальщик.

BlowfishCryptoService - шифрует данные алгоритмом Blowfish, передается в крипто-бокс.

InMemorySecureStore - класс для хранения данных в оперативной памяти телефона

KeyChainSecureStore - Инкапсулирует логику сохранения, загрузки и удаленния данных из keyChain, можно использовать отдельно от криптобоксов

GenericPasswordQueryable - создает query c kSecClassGenericPassword для keyChain, инжектится в KeyChainSecureStore

Использование: Для начала в проекте следует определить константы для криптобокса и для удобства использования можно написать подобный конфигуратор

import CryptoSwift 

struct PinCryptoBoxConfigurator {
    func produceClear() -> PinCryptoBox {
        return PinCryptoBox(secureStore: { Settings.shared.secureStorage },
                            hashProvider: SHA3(variant: .sha224),
                            cryptoService: BlowfishCryptoService(),
                            ivKey: Const.ivKey,
                            dataKey: Const.dataKey,
                            saltKey: Const.saltKey,
                            hashKey: Const.hashKey)
    }
}

Инициализируем сервис, шифруем и дешифруем

let cryptoService = PinCryptoBoxConfigurator().produceClear()
try? cryptoService.encrypt(data: token, auth: pin)
let token = try? cryptoService.decrypt(auth: pin) 

Для обновления данных используем HackCryptoBox

let cryptoService = PinCryptoBoxConfigurator().produceClear().hack()

MailSender

Утилита для посылки email сообщений. Автоматически определяет через какой источник можно послать email.

Алгоритм работы:

  • если можно, то открывает MFMailComposeViewController
  • если можно, то перебрасывает пользователя в стандартное приложение почты
  • иначе - показывает ошибку

Для того, чтобы интегрировать утилиту необходимо в инициализатор передать сущности, которые реализую следующие протоколы:

  • MailSenderErrorDisplaying - сущность, которая будет отображать ошибку
  • MailSenderPayloadProvider - сущность, которая вернет утилите payload со всей необходимой информацией для отсылания email-а
  • MailSenderRouterHelper - сущность, которая умеет показывать, скрывать экраны (router)

Пример интеграции

final class ProjectMailSenderErrorDisplaying: MailSenderErrorDisplaying {

    func display(error: MailSenderError) {
        SnackService().showErrorMessage("There is an error while sending email")
    }

}

final class ProjectMailSenderPayloadProvider: MailSenderPayloadProvider {

    func getPayload() -> MapUtilPayload {
        let body = "Some info about app"
        return MapUtilPayload(
            recipient: "[email protected]",
            subject: "Feedback",
            body: body
        )
    }

}

final class ProjectMailSenderRouterHelper: MailSenderRouterHelper {

    // MARK: - Private Properties

    private let router: Router

    // MARK: - Initializaion

    init(router: Router) {
        self.router = router
    }

    // MARK: - MailSenderRouterHelper

    func present(_ viewController: UIViewController) {
        router.present(viewController)
    }

    func dismiss() {
        router.dismissModule()
    }

}

Пример вызова описанной конфигурации:

let mailSender = MailSender(
    errorDisplaying: ProjectMailSenderErrorDisplaying(),
    payloadProvider: ProjectMailSenderPayloadProvider(),
    routerHelper: ProjectMailSenderRouterHelper(router: MainRouter())
)
mailSender.send()

MoneyModel

Это структура для работы с деньгами:

        print(MoneyModel(decimal: 10, digit: 0).asString()) // выведет -- "10"
        print(MoneyModel(decimal: 10, digit: 9).asString()) // выведет -- "10.09"
        print(MoneyModel(decimal: 10, digit: 99).asString()) // выведет -- "10.99"

MapRoutingService

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

/// получение списка возможных приложений, которые можно отобразить для выбора пользователю
let apps = service.availableApplications

/// построение маршрута
service.buildRoute(to: point, in: app, onComplete: nil)

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

public protocol MapRoutingLocationServiceInterface: AnyObject {
    /// Равно true, когда пользователь разрешил доступ к геопозиции
    var isLocationAccessAllowed: Bool { get }
    /// Равно true, когда пользователь разрешил использование точной геопозиции
    var isAllowedFullAccuracyLocation: Bool { get }
    /// Вовращает текущую геопозицию пользователя, если она известна,
    /// и nil во всех остальных случаях
    func getCurrentLocation(_ completion: @escaping ((CLLocationCoordinate2D?) -> Void))
}

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