Программирование SwiftARC и памятьРазработчик приложений на Swift

Сохраняет ли экземпляр класса время жизни после помещения в значение типа Any?

Сохраняет ли экземпляр класса время жизни после помещения в значение типа Any?

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

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

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

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

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

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

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

Представим очередь событий типа [Any], куда временно помещаются объекты. Разработчик может считать, что после обнуления исходной переменной объект исчезнет, но элемент очереди продолжает содержать сильную ссылку.

Неверное ожидание приводит к росту удерживаемого графа объектов, задержке вызова deinit и увеличению пикового потребления памяти. Особенно опасно хранить в Any крупные объекты или структуры, которые сами содержат сильные ссылки.

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

При присваивании экземпляра класса значению типа Any контейнер сохраняет это значение с сильным владением объектом. Исходная переменная и контейнер становятся независимыми сильными владельцами одного экземпляра.

final class ImageBuffer { deinit { print("deinit") } } var image: ImageBuffer? = ImageBuffer() var storage: Any = image! image = nil print("object is still retained") storage = 0 // после освобождения storage объект доступен для уничтожения

После image = nil объект всё ещё удерживается storage. Когда storage заменяется целым числом, прежнее содержимое освобождается; если это была последняя сильная ссылка, ARC запускает уничтожение объекта и затем deinit.

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

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

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

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

В системе логирования события складывались в массив [Any], чтобы поддержать разные типы payload. После обработки массив очищался не сразу, а только после отправки пачки. В результате крупные объекты, помещённые в payload, удерживались дольше ожидаемого, хотя их исходные переменные уже исчезли.

Рассматривались варианты:

  • хранить объекты в [Any] — просто и гибко, но владение неочевидно и время жизни может продлеваться;
  • хранить только идентификаторы — минимальное удержание памяти, но требуется отдельное хранилище и повторный поиск;
  • использовать явно слабые обёртки — объекты не удерживаются, но к моменту обработки значение может стать nil.

Выбрали идентификаторы для крупных ресурсов, а Any оставили только для небольших значений-данных. Это сделало владение явным, устранило неожиданное удержание объектов и сохранило поддержку разнородных payload.

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

  1. Является ли присваивание значения типа Any копированием экземпляра класса?

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

  1. Что произойдёт, если Any хранит структуру со ссылочным свойством?

Контейнер удерживает структуру, а структура — объект через своё сильное свойство. Копирование такой структуры обычно не копирует экземпляр класса, а увеличивает число владельцев этого экземпляра. Поэтому уничтожение одного значения Any не обязано вызывать deinit вложенного объекта.

  1. Можно ли сделать хранение объекта в Any слабым без дополнительной обёртки?

Нет, сам тип Any не предоставляет слабой семантики хранения. Для этого нужна отдельная ссылочная обёртка с weak-свойством либо специализированное хранилище; тогда Any будет сильно удерживать обёртку, но не объект, на который она ссылается.