В сценарии с перечислением, поддерживающим Equatable, как метки связанных значений влияют на сравнение его экземпляров?
Метки связанных значений не участвуют в сравнении экземпляров перечисления. При синтезированном Equatable Swift сравнивает имя case и сами связанные значения в порядке их объявления. Метки влияют на читаемость и синтаксис вызова, но не становятся частью сравниваемого состояния.
Перечисления в Swift могут описывать не только фиксированный набор состояний, но и переносить данные вместе с каждым case. Метки связанных значений нужны для выразительного публичного интерфейса: они помогают понять смысл параметров при создании значения и сопоставлении с шаблоном.
При этом имя case и его данные должны оставаться отдельными понятиями. Иначе изменение исключительно имени параметра сделало бы логически одинаковые значения разными, что усложнило бы автоматическое сравнение и изменение API.
Рассмотрим перечисление, где case хранит значение с меткой id. Можно ошибочно предположить, что id является частью состояния объекта и влияет на ==.
Такое предположение приводит к неверным решениям: разработчик может вручную реализовать Equatable, сравнивать строки меток или считать переименование метки изменением значения. На самом деле при синтезированном сравнении учитываются только выбранный case и его данные.
Для перечисления с синтезированным Equatable Swift проверяет два условия:
Метки связанных значений являются именами параметров case на уровне исходного кода. Они не хранятся в экземпляре как отдельные значения и не участвуют в работе оператора ==.
В первом сравнении совпадают case data и значение 42, поэтому результатом будет true. Во втором связанные значения имеют одинаковый тип и число, но выбран другой case, поэтому результатом будет false.
Для автоматического синтеза Equatable все связанные значения должны поддерживать Equatable. Если это невозможно или требуется другая бизнес-логика, можно написать собственную реализацию ==; тогда разработчик сам определяет, какие свойства сравнивать. Это даёт гибкость, но создаёт риск нарушить ожидаемое соответствие между равенством и хешированием, если тип также реализует Hashable.
В сетевом слое есть состояние загрузки: успешный результат хранит идентификатор ресурса, а ошибка — код ошибки. Команда переименовала метку id в resourceID, не меняя тип и значение данных.
Вариант с ручным сравнением строковых меток избыточен и хрупок: такие метки не являются частью runtime-состояния. Вариант с отдельным классом для каждого состояния усложняет модель и добавляет ссылочную семантику без необходимости.
Оптимальное решение — оставить перечисление с синтезированным Equatable и считать метки частью читаемого API. Тогда переименование метки не меняет результат сравнения, а изменение самого идентификатора или case корректно меняет результат. Это сохраняет простую семантику значений и уменьшает объём ручного кода.
1. Достаточно ли, чтобы связанные значения имели одинаковые типы, для синтеза Equatable?
Нет. Каждый тип связанного значения должен сам соответствовать Equatable. Например, перечисление с String и Int может получить синтезированное сравнение, поскольку оба типа поддерживают этот протокол. Если среди данных есть тип без Equatable, синтез не выполняется автоматически.
2. Можно ли считать два разных case равными, если их связанные значения совпадают?
При стандартной реализации — нельзя. Сначала сравнивается сам case, и только затем связанные значения соответствующих case. Поэтому data(id: 42) и error(code: 42) не равны, даже если их единственные числовые значения одинаковы.
3. Что нужно учитывать при собственной реализации Equatable для перечисления?
Нужно явно сравнить все значимые варианты и связанные данные, не используя метки как самостоятельное состояние. Если тип также реализует Hashable, равные значения обязаны иметь одинаковый хеш. Иначе коллекции вроде Set и словари могут работать некорректно: значение будет считаться равным ключу, но попадёт в другой хеш-столбец.