В приложении объект записан в статическое свойство, а все локальные ссылки на него обнулены. Когда ARC сможет уничтожить этот объект?
ARC не сможет уничтожить объект, пока статическое свойство хранит на него сильную ссылку. Объект станет доступен для уничтожения только после присваивания этому свойству nil либо замены значения другим объектом, если это изменяемое статическое свойство.
ARC появился как автоматизация ручного управления подсчётом ссылок: разработчику больше не нужно явно вызывать операции удержания и освобождения объектов. Однако ARC по-прежнему опирается на владение ссылками, поэтому глобальные и статические сильные ссылки остаются корнями графа объектов.
Такая модель удобна для общих сервисов, кэшей и конфигурации, но легко приводит к объектам, живущим дольше ожидаемого. В отличие от локальной ссылки, статическое хранилище обычно существует на протяжении всего времени работы процесса.
Локальная переменная может исчезнуть после выхода из функции, но это не означает, что объект будет уничтожен. Если тот же объект записан в статическое свойство, сильное владение продолжает существовать независимо от локальной области видимости.
Ошибочное ожидание немедленного вызова deinit может привести к удержанию больших графов объектов, незакрытым ресурсам и некорректным тестам, в которых состояние статического хранилища сохраняется между запусками.
Статическое свойство ссылочного типа по умолчанию является сильным хранилищем. ARC учитывает эту ссылку так же, как сильную ссылку в свойстве экземпляра: пока она существует, счётчик сильных ссылок объекта не достигает нуля.
Присваивание nil локальной переменной уменьшает число сильных ссылок только со стороны этой переменной. После этого объект остаётся жив благодаря Storage.session. Когда статическое свойство обнуляется, ARC уменьшает число сильных ссылок ещё раз; если других владельцев нет, объект становится доступен для деинициализации.
Для static let принудительное обнуление невозможно, поэтому ссылка обычно сохраняет объект до конца жизни статического хранилища. У static var владельца можно явно освободить, но доступ к изменяемому глобальному состоянию нужно дополнительно согласовывать между потоками: ARC управляет временем жизни, но не делает операции с переменной синхронными.
Важно различать доступность объекта для уничтожения и фактическое освобождение памяти. После достижения нулевого числа сильных ссылок deinit обычно вызывается сразу в рамках ARC-операции, но освобождение памяти аллокатором и возврат страниц операционной системе могут происходить позже.
Команда хранит текущий экран в статическом свойстве для доступа из разных частей приложения. После перехода на новый экран старый экран продолжает удерживать модель данных, подписки и изображения, поэтому память не освобождается.
Варианты решения:
static let — просто, но объект нельзя освободить до завершения процесса;static var и заменять новым экземпляром — подходит для единственного текущего объекта, но требует явного управления заменой;weak — предотвращает удержание объекта, но объект может исчезнуть сразу после потери других сильных владельцев;Для текущей сессии выбирают static var с явной очисткой при завершении сценария. Это сохраняет предсказуемое владение, позволяет вызвать deinit вовремя и не создаёт скрытой зависимости от того, остались ли локальные ссылки.
1. Может ли static let удерживать объект, если исходная локальная ссылка уже уничтожена?
Да. После присваивания объекту static let это свойство становится его сильным владельцем. Уничтожение локальной ссылки уменьшает только локальное владение, а объект продолжает жить благодаря неизменяемому статическому свойству.
2. Освободит ли объект присваивание нового значения в static var?
Старое значение становится доступным для уничтожения после замены, если статическое свойство было его последней сильной ссылкой. Сначала ARC должен сохранить новое значение, затем освободить старое; поэтому при наличии других владельцев deinit старого объекта не вызовется.
3. Гарантирует ли обнуление статического свойства немедленное уменьшение памяти процесса?
Нет. Обнуление может немедленно сделать объект недостижимым и запустить его deinit, но освобождённые блоки памяти могут остаться в куче для повторного использования. Кроме того, объект может быть частью другого удерживаемого графа или иметь внешние ресурсы, освобождение которых происходит отдельно.