Сравните поведение слабой и невладеющей ссылки при обращении после уничтожения объекта.

Сравните поведение слабой и невладеющей ссылки при обращении после уничтожения объекта.

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

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

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

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

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

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

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

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

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

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

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

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

weak хранится без сильного владения и имеет optional-тип. Когда объект уничтожается, ARC обнуляет все weak-ссылки на него. Поэтому чтение такой ссылки после уничтожения возвращает nil, но доступ требует безопасной распаковки или проверки.

unowned также не увеличивает число сильных ссылок, но не обнуляется. Обычная unowned-ссылка сохраняет информацию о том, на какой объект она указывала; если объект уже уничтожен, проверяемый runtime-доступ к ссылке вызывает trap. Это не способ «попробовать обратиться», а утверждение о гарантии времени жизни.

final class Session {} final class Screen { weak var observedSession: Session? unowned let requiredSession: Session init(session: Session) { requiredSession = session } } var weakReference: Session? do { let session = Session() weakReference = session } print(weakReference == nil) // true

В примере weakReference становится nil, когда локальная сильная ссылка session исчезает. Доступ к requiredSession после уничтожения переданного Session был бы ошибкой времени выполнения.

Выбор обычно определяется гарантией времени жизни:

  • weak — когда объект может исчезнуть раньше ссылки или это зависит от внешнего управления; цена — optional-проверки и дополнительная работа runtime для обнуления.
  • unowned — когда жизненный контракт строгий и доказуемый; код не требует optional-доступа, но нарушение контракта вызывает аварийное завершение.
  • unowned(unsafe) — ещё более рискованный вариант без проверки жизнеспособности ссылки; после уничтожения объекта поведение небезопасно, поэтому применять его следует только при очень строгом контроле времени жизни.

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

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

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

Вариант с unowned предполагает, что экран гарантированно переживёт операцию. Его плюс — отсутствие optional-проверок, но при позднем завершении операции приложение аварийно завершится при обращении к экрану.

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

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

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

  1. Вопрос: может ли weak-ссылка продлить время жизни объекта косвенно?

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

  2. Вопрос: достаточно ли объявить ссылку как unowned, чтобы цикл ссылок исчез?

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

  3. Вопрос: чем отличается обычная unowned от unowned(unsafe) после уничтожения объекта?

    Обычная unowned-ссылка проверяется runtime и при обращении к уничтоженному объекту приводит к контролируемому trap. unowned(unsafe) отключает эту проверку; обращение после уничтожения объекта небезопасно и не должно использоваться как штатный способ оптимизации. Такой вариант допустим только при доказуемой гарантии, что ссылка никогда не будет прочитана после завершения жизни объекта.