Стандартные протоколы
Вакансии развешивает не только ваша программа. Стандартная библиотека Swift заранее повесила свои, и её операторы и функции смотрят на значки:
==сравнивает только значения с протоколомEquatable;sorted()сортирует толькоComparable;printищет у значенияCustomStringConvertible, чтобы узнать, как его показать;Setи ключи словаря принимают толькоHashable.
Без значка даже сравнение двух точек не скомпилируется. Если объявить struct Point без протокола, строка a == b даст ошибку:
error: binary operator '==' cannot be applied to two 'Point' operandsEquatable: равны ли два значения
Наденем на точку значок Equatable:
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: что меньше
Чтобы значения можно было сортировать, тип должен уметь сказать, какое из двух меньше:
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Давайте разберём, что здесь происходит:
Comparableтребует оператор<. Сам Swift его не напишет: он не знает, по какому свойству вы хотите сравнивать.- Оператор объявляют как метод типа,
static func, только вместо имени пишут сам знак<. - Операторы
>,<=и>=стандартная библиотека выводит из<, а==Swift пишет сам: требованияEquatableвходят вComparable. - Теперь
sorted()расставляет результаты от меньшего к большему.
CustomStringConvertible: как печатать
Наденем на счёт ещё один значок, на этот раз расширением:
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.
Без значка множество точку не примет:
error: generic struct 'Set' requires that 'Point' conform to 'Hashable'Частая ошибка
Свойство description написали, а соответствие протоколу не объявили:
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, а===проверяет, один ли это объект класса, и протокола не требует.