Удерживает ли словарь экземпляр класса, сохранённый в качестве ключа?

Удерживает ли словарь экземпляр класса, сохранённый в качестве ключа?

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

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

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

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

ARC управляет временем жизни объектов через владение ссылками. Коллекции Swift имеют семантику значений, но это не означает копирование самих экземпляров классов: при копировании словаря копируются ссылки, а не объекты.

Такой подход позволяет безопасно хранить объекты в коллекциях без ручного управления retain/release и одновременно сохранять предсказуемое правило: владеющая коллекция продлевает жизнь помещённых в неё объектов.

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

Разработчик может считать словарь временным индексом и ожидать, что удаление последней локальной ссылки уничтожит объект. Это неверно, если объект всё ещё используется как ключ или значение словаря.

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

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

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

Удаление записи освобождает сильные ссылки словаря на ключ и значение. После этого объект становится доступным для уничтожения только при отсутствии других сильных владельцев; наличие слабых или unowned-ссылок само по себе его не удерживает.

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

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

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

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

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

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

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

  1. Продлевает ли копирование словаря жизнь объектов, находящихся в нём?

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

  2. Вызовет ли удаление записи deinit ключа немедленно?

    Не обязательно. Удаление записи убирает сильное владение словаря, но объект может удерживаться локальной переменной, другой коллекцией, замыканием, задачей или свойством другого объекта. Если словарь был последним владельцем, объект становится доступным для уничтожения, однако полагаться на deinit как на механизм управления внешним ресурсом не следует.

  3. Можно ли сделать словарь слабым, просто объявив его ключи или значения опциональными?

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