Фантомные типы в Swift

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Не каждый язык со статической системой типов обладает такой строгой типобезопасностью, как Swift. Это стало возможным благодаря таким особенностям Swift, как фантомные типы (phantom types), расширения универсальных типов и перечисления со связанными типами. На этой неделе мы узнаем, как использовать фантомные типы для создания типобезопасных API.

Основы

Фантомный тип — это универсальный тип, который объявляется, но никогда не используется внутри типа, в котором он объявлен. Обычно он используется как общее ограничение для создания более надежного и безопасного API. Рассмотрим простой пример.

struct Identifier<Holder> {
    let value: Int
}

В приведенном выше примере у нас есть структура Identifier с объявленным универсальным типом Holder. Как видите, мы не используем тип Holder внутри типа Identifier. Поэтому этот тип называют фантомным. Теперь давайте подумаем о преимуществах использования подобных типов.

struct User {
    let id: Identifier<Self>
}

struct Product {
    let id: Identifier<Self>
}

let product = Product(id: .init(value: 1))
let user = User(id: .init(value: 1))

user.id == product.id

Создадим типы User (пользователь) и Product (продукт), воспользовавшись ранее созданной структурой Identifier. Установим значение идентификатора равным 1 для новых типов user и product. Но если мы попытаемся их сравнить, компилятор Swift выдаст ошибку:

Двоичный оператор «==» не может применяться к операндам типа Identifier-User и Identifier-Product.

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

func fetch(_ product: Identifier<Product>) -> Product? {
    // return product by id
}

fetch(user.id)

Типобезопасность в HealthKit

Мы изучили основы фантомных типов. Теперь мы можем перейти к более сложным примерам. Я создал пару приложений для поддержания здоровья, которые используют HealthKit для хранения и запроса данных о состоянии пользователя от Apple Watch. Рассмотрим типичный пример кода, получающий данные из приложения Apple Health.

import HealthKit

let store = HKHealthStore()
let bodyMass = HKQuantityType.quantityType(
    forIdentifier: HKQuantityTypeIdentifier.bodyMass
)!
let query = HKStatisticsQuery(
    quantityType: bodyMass,
    quantitySamplePredicate: nil,
    options: .discreteAverage
) { _, statistics, _ in
    let average = statistics?.averageQuantity()
    let mass = average?.doubleValue(for: .meter())
}

store.execute(query)

В приведенном выше примере мы создаем запрос для получения веса пользователя из приложения Apple Health. В обработчике завершения мы пытаемся получить среднее значение и преобразовать его в метры. Как нетрудно догадаться, преобразовать массу тела в метры невозможно, и здесь приложение вылетает. Постараемся решить эту проблему, введя фантомный тип для создания более типобезопасного API.

enum Distance {
    case mile
    case meter
}

enum Mass {
    case pound
    case gram
    case ounce
}

struct Statistics<Unit> {
    let value: Double
}


extension Statistics where Unit == Mass {
    func convert(to unit: Mass) -> Double {

    }
}

extension Statistics where Unit == Distance {
    func convert(to unit: Distance) -> Double {

    }
}

let weight = Statistics<Mass>(value: 75)
weight.convert(to: Distance.meter)

Вот возможное решение для фреймворка HealthKit, где для повышения безопасности API используется фантомный тип. Мы вводим перечисления Mass (масса) и Distance (расстояние), чтобы работать с различными единицами измерения. Как только вы попытаетесь преобразовать массу в расстояние, компилятор Swift остановит вас, отобразив сообщение об ошибке:

Невозможно преобразовать значение типа Distance в ожидаемый тип аргумента Mass.

Заключение

Сегодня мы изучили фантомные типы, одну из моих любимых функций в языке Swift. Очевидно, существует множество возможных применений фантомных типов. Не стесняйтесь рассказать о своих способах повышения безопасности API с помощью фантомных типов. Надеюсь, вам понравится этот пост. Читайте мои посты в Twitter и задавайте вопросы по этой статье. Спасибо за внимание и до следующей недели!


В преддверии старта курса "iOS Developer. Professional", приглашаем всех желающих на бесплатный демо-урок по теме: "Machine Learning в iOS с помощью CoreML и CreateML".

ЗАПИСАТЬСЯ НА ДЕМО-УРОК


Источник: https://habr.com/ru/company/otus/blog/556990/


Интересные статьи

Интересные статьи

Представим, нам нужно внести небольшую правку в работу экрана. Экран меняется каждую секунду, поскольку в нем одновременно происходит множество процессов. Как правило, чтобы урегу...
Всем привет. Когда я искал информацию о журналировании (аудите событий) в Bitrix, на Хабре не было ни чего, в остальном рунете кое что было, но кто же там найдёт? Для пополнения базы знаний...
Если вы использовали SwiftUI, то наверняка обращали внимание на такие ключевые слова, как @ObservedObject, @EnvironmentObject, @FetchRequest и так далее. Property Wrappers (далее «обёртки свойств...
Swift UI — галопом по Европам 22:35. Восторг Просмотрел WWDC 2019 Key Notes. Ожидаемый декларативный UI действительно стал явью, и это воистину событие вселенского масштаба для мира iOS-разра...
Вводная частьВ предыдущем посте я писал как организовать процесс “грумминга” задач в системе JIra так чтобы “Менеджеру продукта” было удобно осуществлять навигацию по всему Беклогу продукта. Прод...