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

Ситуация: объект гарантированно живёт дольше ссылки, но ссылка не должна удерживать его; к чему приведёт обращение к unowned-ссылке после освобождения объекта?

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

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

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

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

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

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

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

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

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

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

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

После освобождения объекта unowned-ссылка не обнуляется. Она остаётся ссылкой на уже недействительный объект, а попытка чтения или вызова через неё вызывает runtime trap. Само наличие такой ссылки не обязательно немедленно приводит к ошибке — ошибка возникает при обращении.

final class Owner { func notify() { print("уведомление") } } final class Child { unowned let owner: Owner init(owner: Owner) { self.owner = owner } func notifyOwner() { owner.notify() } } var child: Child? do { let owner = Owner() child = Child(owner: owner) } child?.notifyOwner() // аварийное завершение: Owner уже освобождён

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

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

Экран удерживает дочерний контроллер, а дочерний контроллер обращается к экрану-владельцу. Если обе ссылки сильные, возникает цикл: экран удерживает контроллер, контроллер удерживает экран, и ARC не освобождает ни один объект.

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

Вариант с unowned не создаёт цикла и сохраняет простой необязательный контроль потока: вызов предполагает наличие владельца. Его следует выбирать только при гарантии, что контроллер не переживёт экран или не будет использоваться после его уничтожения. Иначе предпочтительнее weak с явной обработкой nil.

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

  1. Чем unowned отличается от weak по поведению после освобождения объекта?

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

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

  1. Создаёт ли unowned-ссылка цикл сильных ссылок?

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

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

  1. Может ли unowned быть необязательной ссылкой?

Да, Swift поддерживает необязательную unowned-ссылку, например unowned var parent: Parent?. Она по-прежнему не удерживает объект, но может содержать nil и быть явно обнулена.

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