Почему расширение типа в Swift не может добавить ему сохранённое свойство?
Расширение не может добавить сохранённое свойство, потому что оно не меняет физическое представление экземпляра и размер уже объявленного типа. В расширении разрешены вычисляемые свойства, методы, вложенные типы и соответствия протоколам, но для хранения нового состояния нужно изменить основное объявление типа либо использовать внешнее хранилище.
Расширения появились как способ разделять реализацию типа, организовывать крупные объявления и добавлять соответствия протоколам вне основного файла. При этом модель Swift сохраняет границу между добавлением поведения и изменением памяти экземпляра.
Сохранённое свойство требует места в layout типа, правил инициализации, копирования, уничтожения и, для некоторых типов, совместимости с уже скомпилированным представлением. Разрешение таких изменений из произвольного расширения сделало бы структуру типа зависимой от порядка и состава независимых деклараций.
Предположим, расширение протокола должно добавить к каждому соответствующему типу внутренний кэш. Наивное решение — объявить в расширении сохранённое свойство, но это невозможно: протокол и его расширение не владеют единым универсальным хранилищем экземпляра.
Попытка заменить его вычисляемым свойством без отдельного хранилища не решает задачу: значение придётся вычислять заново или хранить где-то ещё. Ошибка в выборе подхода может привести к потере кэширования, проблемам с потокобезопасностью или неоправданной зависимости от конкретного класса.
Расширение может добавить поведение, которое вычисляется из уже существующего состояния, но не новые биты состояния. Поэтому такой код допустим:
fullName — вычисляемое свойство: оно не требует изменения памяти Person и использует требования протокола. Если бы расширение добавляло сохранённое свойство, компилятору пришлось бы включить его в layout каждого соответствующего типа, включая типы, исходный код которых расширение не контролирует.
Это ограничение одинаково важно для обычных расширений типов и расширений протоколов. Протокол описывает требования, а его расширение может предоставить реализацию или производное представление данных, но не может навязать всем conforming types одинаковое поле хранения.
Для структур и перечислений состояние обычно размещают в основном объявлении типа. Для классов возможен подкласс, если архитектура допускает изменение конкретного типа. Если нужно добавить состояние к уже существующим экземплярам классов без изменения их объявления, применяют внешнее хранилище, например таблицу по идентификатору объекта; такой вариант требует контроля жизненного цикла и синхронизации.
Внешнее хранилище — компромисс, а не полная замена сохранённому свойству: оно может потребовать блокировок, очистки записей и обработки коллизий идентификаторов. Поэтому для общего протокольного поведения лучше сначала проверить, можно ли выразить результат как вычисляемое свойство или передать состояние через сам тип.
Команде нужно добавить к нескольким моделям протокол Displayable, а в его расширении — кэш форматированного текста. Рассматривались три варианта.
Первый — сохранить кэш в расширении протокола. Он невозможен: расширение не может добавить поле каждому conforming type. Второй — использовать вычисляемое свойство, но тогда форматирование выполняется при каждом обращении, если кэш не вынесен отдельно.
Третий — сделать кэш частью каждой модели или передавать форматтер с внешним кэшем. Для небольшого числа моделей выбран первый из этих рабочих вариантов: состояние добавили в основные объявления типов, потому что это проще, быстрее и безопаснее с точки зрения жизненного цикла. Внешний кэш оставили для объектов, которыми нельзя управлять напрямую.
1. Может ли вычисляемое свойство в расширении иметь private вспомогательное состояние?
Само по себе — нет. private ограничивает область видимости, но не превращает свойство в хранилище и не позволяет расширению добавить поле экземпляра. Вспомогательное состояние всё равно должно находиться в основном типе или во внешнем хранилище.
2. Почему запрет особенно важен для расширения протокола, а не только для расширения конкретной структуры?
Расширение протокола может применяться к множеству заранее неизвестных типов: структурам, классам и перечислениям с разным layout и разными правилами инициализации. У протокола нет единого экземпляра, в который можно было бы встроить поле, поэтому он может задавать требования и реализацию поведения, но не общее физическое хранилище.
3. Решает ли проблему добавление свойства с property wrapper в расширении?
Нет. Property wrapper обычно создаёт скрытое backing storage, а именно новое сохранённое состояние в расширении и запрещено. В расширении можно объявить вычисляемую обёртку, но её состояние должно храниться в доступном основном свойстве или во внешнем контейнере.