Перейти к содержимому
Теория · Протоколы и обобщённый код · урок 6 из 9

Стандартные протоколы

Вакансии развешивает не только ваша программа. Стандартная библиотека Swift заранее повесила свои, и её операторы и функции смотрят на значки:

  • == сравнивает только значения с протоколом Equatable;
  • sorted() сортирует только Comparable;
  • print ищет у значения CustomStringConvertible, чтобы узнать, как его показать;
  • Set и ключи словаря принимают только Hashable.

Без значка даже сравнение двух точек не скомпилируется. Если объявить struct Point без протокола, строка a == b даст ошибку:

TEXT
error: binary operator '==' cannot be applied to two 'Point' operands

Equatable: равны ли два значения

Наденем на точку значок Equatable:

SWIFT
struct Point: Equatable {
    let x: Int
    let y: Int
}

let a = Point(x: 1, y: 2)
let b = Point(x: 1, y: 2)
print(a == b, a != Point(x: 2, y: 1)) // true true

Требование Equatable — оператор ==. Писать его не пришлось: Swift написал его сам, потому что оба свойства точки — Int, а числа он сравнивать умеет. Две точки равны, когда равны все их свойства. Оператор != прилагается к ==.

Swift сам пишет == для структур и перечислений. Классу == придётся написать самому: class Ticket: Equatable без своего == не скомпилируется.

Здесь картинка с вакансиями неточна. К обычной работе кандидат готовится сам, а к Equatable структуру часто готовит Swift: достаточно надеть значок.

== и ===

Этот вопрос часто задают на собеседовании: чем == отличается от ===?

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

Два билета Ticket(seat: 5), созданные по отдельности, равны по ==, если классу написали == по номеру места. Но === между ними даёт false: объектов два.

Comparable: что меньше

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

SWIFT
struct Score: Comparable {
    let points: Int
    static func < (a: Score, b: Score) -> Bool {
        a.points < b.points
    }
}

let scores = [Score(points: 7), Score(points: 3), Score(points: 5)]
let ranked = scores.sorted()
print(ranked[0].points, ranked[2].points) // 3 7
print(Score(points: 2) < Score(points: 5)) // true

Давайте разберём, что здесь происходит:

  1. Comparable требует оператор <. Сам Swift его не напишет: он не знает, по какому свойству вы хотите сравнивать.
  2. Оператор объявляют как метод типа, static func, только вместо имени пишут сам знак <.
  3. Операторы >, <= и >= стандартная библиотека выводит из <, а == Swift пишет сам: требования Equatable входят в Comparable.
  4. Теперь sorted() расставляет результаты от меньшего к большему.

CustomStringConvertible: как печатать

Наденем на счёт ещё один значок, на этот раз расширением:

SWIFT
extension Score: CustomStringConvertible {
    var description: String { "\(points) pts" }
}

print(ranked[0]) // 3 pts
print("best: \(ranked[2])") // best: 7 pts

Требование протокола — свойство description, которое можно прочитать. print и подстановка в строку берут текст из него. Здесь description — вычисляемое свойство, поэтому текст собирается заново при каждой печати и показывает текущие значения. Без значка print показал бы тип и его поля: Score(points: 3).

Hashable: для множеств и словарей

Элементы Set и ключи словаря, как вы уже знаете, должны быть Hashable. Значок Hashable надевают так же, как Equatable: если все хранимые свойства Hashable, Swift напишет нужное сам. После строки extension Point: Hashable {} точки можно класть во множество, и Set([a, b]) хранит одну точку, потому что a == b.

Без значка множество точку не примет:

TEXT
error: generic struct 'Set' requires that 'Point' conform to 'Hashable'

Частая ошибка

Свойство description написали, а соответствие протоколу не объявили:

SWIFT
struct Day {
    var n: Int
    var description: String { "day \(n)" }
}

print(Day(n: 4)) // ✗

Программа компилируется, но печатает Day(n: 4), а не day 4. Одного свойства с нужным именем мало: print берёт description только у типа со значком CustomStringConvertible. Исправление — struct Day: CustomStringConvertible.

Главное

  • Стандартные операторы и функции требуют значков: == — Equatable, sorted() — Comparable, Set и ключи словаря — Hashable.
  • Если все хранимые свойства структуры сами Equatable или Hashable, ей достаточно объявить этот значок: нужное Swift напишет сам. Для Comparable нужен свой оператор <.
  • print и подстановка берут текст из description, только если тип объявил CustomStringConvertible.
  • == сравнивает значения и требует Equatable, а === проверяет, один ли это объект класса, и протокола не требует.