Объясните механизм, из-за которого копирование структуры со ссылочным свойством не делает состояние объекта независимым.
Копирование структуры копирует значение каждого её свойства. Если свойство содержит ссылку на экземпляр класса, копируется сама ссылка, поэтому обе структуры указывают на один объект и видят его последующие изменения. Value semantics внешней структуры не распространяются автоматически на состояние вложенного ссылочного объекта.
Swift разделяет значимые типы и ссылочные типы, чтобы поведение копирования было предсказуемым. Структуры обычно моделируют независимые значения, а классы — идентичные объекты, которыми могут владеть и управлять несколько частей программы.
Такое разделение позволяет выбирать между безопасной изоляцией состояния и совместным доступом к объекту. Однако композиция этих типов не меняет семантику автоматически: структура, содержащая класс, всё ещё может делить его состояние.
Представим структуру конфигурации, в которой хранится объект класса с изменяемыми данными. После копирования конфигурации разработчик может ожидать, что изменения копии не затронут оригинал, но обе структуры будут содержать одну ссылку.
Это приводит к неожиданным побочным эффектам: изменение одной копии влияет на другую, усложняет тестирование и может вызвать гонки данных при доступе из разных потоков. Ошибка особенно трудно заметна, если внешняя структура выглядит как обычный неизменяемый value type.
При копировании структуры Swift создаёт новую структуру, но для свойства ссылочного типа переносит значение ссылки. В результате появляется вторая оболочка вокруг того же экземпляра класса, а не второй экземпляр объекта.
В примере first и second — разные значения структуры, но first.settings и second.settings указывают на один объект Settings. Поэтому мутация объекта наблюдается через обе структуры.
Чтобы получить независимое состояние, применяют глубокое копирование объекта, проектируют модель как чистый value type или используют контролируемый Copy-on-Write. Поверхностное копирование ссылки дешевле, но сохраняет совместное состояние; глубокое копирование изолирует данные, однако требует затрат памяти и времени.
Важно отличать переназначение свойства структуры от мутации объекта. Если свойство структуры можно заменить, это изменяет конкретную оболочку; изменение доступного свойства самого класса изменяет общий объект. let у ссылки запрещает переназначить ссылку, но не обязательно запрещает менять объект, на который она указывает.
В приложении профиль пользователя хранил структуру состояния с вложенным классом кэша изображений. При создании черновика профиля разработчик копировал структуру, рассчитывая редактировать черновик независимо, но очистка кэша в черновике очищала кэш исходного профиля.
Рассматривались три варианта. Сделать кэш структурой означало бы получить более ясную value semantics, но потребовало бы пересмотра API и могло увеличить стоимость копирования. Создавать полный объект кэша при каждом копировании обеспечило бы изоляцию, но было бы дорого для большого кэша. Оставить общую ссылку было проще, но не соответствовало требованиям черновика.
Выбрали отдельный объект кэша для черновика с явным методом клонирования. Это дало независимое состояние только там, где оно действительно требовалось, а обычные операции по-прежнему использовали дешёвое совместное владение.
let неизменяемость вложенного объекта?Нет. let запрещает изменить переменную структуры или заменить её свойства, но ссылочный объект внутри может оставаться изменяемым, если его свойства допускают мутацию. Неизменяемость оболочки не означает неизменяемость всего графа объектов.
final, чтобы копии структуры стали независимыми?Нет. final запрещает наследование класса, но не меняет его ссылочную семантику. Копирование структуры с таким свойством всё равно скопирует ссылку на тот же экземпляр.
Такой тип перед изменением проверяет, используется ли внутреннее хранилище совместно. Если да, он создаёт независимую копию хранилища и изменяет её; если нет, изменяет существующее хранилище на месте. Пользователь видит поведение значимого типа, хотя реализация использует ссылочный объект для экономии ресурсов.