В публичной Swift-библиотеке набор вариантов ошибки может расшириться без обновления клиента. Как оформить разбор такой ошибки, чтобы новые варианты не ломали двоичную совместимость и не оставались незамеченными компилятором?
Для устойчивого разбора публичной ошибки используйте явные ветки для известных вариантов и @unknown default для будущих. Это сохраняет работоспособность клиента при добавлении новых case и просит компилятор предупредить о таких вариантах при последующей компиляции.
В Swift-библиотеках с включённым режимом library evolution публичные перечисления должны поддерживать развитие API. Если библиотека добавит новый вариант ошибки, уже скомпилированный клиент не должен аварийно завершаться только потому, что его код не знает об этом варианте.
Механизм resilience отделяет известные клиенту варианты от полного набора вариантов, который может измениться в будущей версии библиотеки. Поэтому обработка публичного перечисления должна учитывать неизвестные значения.
Полный switch по текущим вариантам создаёт ложное ощущение исчерпывающей обработки: сегодня все случаи известны, но завтра библиотека может добавить новый. Простая ветка default сохранит выполнение, однако полностью подавит предупреждения о новых известных вариантах.
Атрибут @frozen решает проблему иначе: он фиксирует набор вариантов перечисления для ABI. Это ограничивает развитие публичного типа и обычно не подходит для ошибки, набор причин которой должен расширяться.
Оставляйте отдельные ветки для известных ошибок, а запасной путь оформляйте как @unknown default. Компилятор проверяет, что текущие варианты перечислены явно, и предупреждает, если после обновления SDK появились новые варианты.
@unknown default не перечисляет будущие случаи и не позволяет обратиться к их ассоциированным данным. Он задаёт безопасное поведение по умолчанию, например показ общего сообщения, запись события в журнал или завершение операции без разрушения состояния.
Обычный default стоит применять, когда предупреждение о новых вариантах не нужно. @frozen оправдан только при осознанном обещании, что набор вариантов не изменится; для расширяемой публичной модели ошибки это сильное ограничение.
Сетевой SDK публикует ServiceError, а приложение показывает пользователю разные экраны для авторизации и ограничения частоты запросов. Вариант с обычным default прост и устойчив к новым значениям, но команда может не заметить добавление новой причины и случайно показать слишком общее сообщение.
Вариант без запасной ветки хорошо защищает от пропуска известных случаев, но не подходит для resilient-публичного перечисления: клиент должен иметь поведение для значения, добавленного библиотекой позднее. Вариант с @frozen делает разбор предсказуемым, но лишает библиотеку возможности безопасно расширять набор ошибок.
Оптимален @unknown default: известные причины обрабатываются специализированно, будущие — безопасно, а компилятор сигнализирует разработчику об изменении API. В результате старый бинарный клиент продолжает работать, а новый клиент может явно добавить обработку новой ошибки.
Зачем нужен @unknown default, если уже есть default?
Обычный default просто принимает все значения, которые не попали в предыдущие ветки, и не предназначен для контроля развития перечисления. @unknown default также обеспечивает запасной путь выполнения, но дополнительно позволяет компилятору предупредить о новых вариантах, известных при компиляции обновлённого клиента.
Следует ли использовать @frozen для публичного перечисления ошибок?
Только если библиотека действительно гарантирует неизменность набора вариантов. Для расширяемого API это нежелательно: добавление нового case в замороженное перечисление может нарушить ABI-обещания или потребовать несовместимого изменения API. Для эволюционирующей модели ошибки обычно выбирают resilient-перечисление и @unknown default.
Может ли @unknown default определить конкретную будущую причину ошибки?
Нет. Эта ветка сообщает лишь о том, что значение не совпало с известными вариантами; имя нового case и его ассоциированные данные недоступны через неё. Поэтому запасная обработка должна быть общей и безопасной, а для детализации после обновления библиотеки нужно добавить отдельную явную ветку.