Программирование SwiftARC и памятьМладший iOS-разработчик на Swift

Объясните механизм: что происходит с экземпляром класса при копировании структуры, содержащей сильное свойс...

Объясните механизм: что происходит с экземпляром класса при копировании структуры, содержащей сильное свойство-ссылку на этот экземпляр?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Копируется сама структура и значение ссылки внутри неё, но не экземпляр класса. Обе копии структуры указывают на один объект, поэтому для сильной ссылки ARC учитывает ещё одного владельца, а объект освобождается только после исчезновения последней сильной ссылки.

Исторический контекст

В Swift структуры имеют семантику значения, а экземпляры классов — семантику ссылки. Такое разделение позволяет передавать небольшие модели как независимые значения, сохраняя возможность явно разделять состояние через классы.

ARC автоматизирует управление временем жизни классовых экземпляров: разработчику не нужно вручную увеличивать и уменьшать число сильных ссылок. При этом ARC не делает глубокие копии объектов и не устраняет побочные эффекты совместного доступа к изменяемому состоянию.

Постановка проблемы

Предположение, что копирование структуры всегда создаёт полностью независимую копию, опасно, если структура содержит ссылочное свойство. После копирования две структуры могут изменять один и тот же экземпляр класса.

Неверное понимание приводит к неожиданным изменениям состояния, преждевременной или, наоборот, более поздней деинициализации объекта и ошибочным попыткам решить проблему с помощью weak или unowned. Эти ссылки меняют владение, но не превращают объект в значение и не создают его копию.

Подробное решение

Если свойство структуры содержит обычную сильную ссылку на объект, копирование структуры создаёт вторую сильную ссылку на тот же экземпляр. Счётчик сильных ссылок увеличивается концептуально; конкретные операции retain и release могут быть оптимизированы компилятором, если наблюдаемая семантика времени жизни сохраняется.

final class Profile { var name: String init(_ name: String) { self.name = name } } struct UserCard { let profile: Profile } let first = UserCard(profile: Profile("Анна")) var second = first second.profile.name = "Ольга" print(first.profile.name) // Ольга

После присваивания second = first свойства first.profile и second.profile ссылаются на один Profile. Изменение через одну структуру видно через другую. Когда одна структура перестаёт существовать, объект обычно продолжает жить благодаря сильной ссылке второй структуры; после удаления последней сильной ссылки он становится доступен для деинициализации.

Это отличается от копирования самого значения. Если требуется независимое состояние, нужно либо хранить в структуре только value-типы, либо явно реализовать глубокое копирование объекта. Простое добавление weak не является решением: слабая ссылка не владеет объектом, поэтому он может исчезнуть, пока структура ещё существует.

Следует учитывать и copy-on-write. Например, стандартные коллекции могут откладывать копирование своего внутреннего буфера до фактического изменения, но наличие внутри коллекции экземпляра класса не делает этот экземпляр независимым. Копирование коллекции и глубокое копирование объектов, находящихся в ней, — разные операции.

Ситуация из практики

В приложении структура состояния экрана содержит ссылочный объект ImageCache. После передачи состояния между слоями один слой очищает кэш, а другой неожиданно теряет изображения, потому что обе структуры используют один объект.

Вариант с weak уменьшит владение, но создаст риск получить nil в момент обращения. Вариант с глубоким копированием изолирует состояние, но может быть дорогим и нарушить намеренное совместное использование кэша. Вариант с неизменяемым или явно разделяемым сервисом сохраняет общую инфраструктуру, но требует чётко определить, кто владеет сервисом и допустимы ли его мутации.

Если кэш действительно должен быть общим, разумно оставить сильную ссылку и явно задокументировать совместное состояние, а изменяемые операции синхронизировать или изолировать актором. Если же состояние должно быть независимым, лучше представить его value-типами либо предоставить явный метод глубокого копирования. Результат должен соответствовать модели владения, а не случайно зависеть от копирования оболочки-структуры.

Что кандидаты часто упускают

Дополнительный вопрос 1: уничтожается ли объект после выхода из области видимости одной из копий структуры?

Нет, если другая копия всё ещё содержит сильную ссылку на тот же объект. Выход одной структуры из области видимости уменьшает число сильных ссылок, но объект становится доступен для уничтожения только после исчезновения последней сильной ссылки. При этом точный момент освобождения локальной ссылки может быть оптимизирован компилятором в пределах наблюдаемой семантики Swift.

Дополнительный вопрос 2: создаст ли Array независимые экземпляры классов при копировании массива?

Нет. Копирование массива может отделить его внутреннее хранилище по правилам copy-on-write, но элементы-классы остаются ссылками на те же экземпляры. Чтобы получить независимые объекты, необходимо явно скопировать каждый элемент или использовать модель данных, основанную на value-типах.

Дополнительный вопрос 3: почему замена сильного свойства структуры на unowned не решает проблему общего изменяемого состояния?

unowned лишь убирает владение объектом и предполагает, что объект гарантированно переживёт ссылку. Он не копирует объект и не изолирует состояние; обе структуры по-прежнему обращаются к одному экземпляру. Если объект будет уничтожен раньше, обращение через unowned приведёт к аварийному завершению, поэтому такую ссылку допустимо применять только при действительно доказанном порядке времени жизни.