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

Что происходит с объектом при самоприсваивании его сильной ссылке?

Что происходит с объектом при самоприсваивании его сильной ссылке?

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

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

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

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

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

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

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

Предположим, сильная ссылка является последней ссылкой на экземпляр и ей присваивают значение, полученное из неё же. Ошибочная последовательность «сначала release старого значения, затем retain нового» привела бы к вызову deinit прямо во время операции и к обращению к уже уничтоженному объекту.

Реальный код может содержать пользовательский setter, вычисляемое свойство или побочные эффекты. Поэтому вывод относится к обычной операции со stored strong-ссылкой, а не к произвольной логике внутри setter.

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

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

Компилятор может оптимизировать лишние операции ARC. Наблюдаемым результатом, однако, должно быть отсутствие преждевременного deinit и сохранение корректного состояния программы; на конкретную последовательность внутренних retain и release полагаться нельзя.

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

final class Item { deinit { print("deinit") } } var item: Item? = Item() item = item print(item != nil) item = nil

После item = item экземпляр остаётся жив, поскольку item продолжает быть сильной ссылкой на него. deinit вызывается только после последующего присваивания nil, если других сильных ссылок нет.

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

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

Вариант с ручным предварительным созданием дополнительной копии ссылки действительно мог временно увеличить число сильных ссылок, но усложнял код и не решал проблему пользовательского setter. Вариант с доверием к правильной семантике ARC был предпочтительнее для обычного stored property: он не добавлял лишнего владения и сохранял ожидаемое время жизни объекта.

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

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

  1. Можно ли использовать точный порядок retain и release как часть логики программы?

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

  1. Изменится ли вывод, если самоприсваивание выполняется через свойство с пользовательским setter?

Да, потенциально изменится поведение операции, но не из-за сбоя ARC. Setter может записывать значение в другое место, обнулять ссылки, запускать callbacks или выполнять произвольный код. ARC корректно управляет ссылками, которые встречаются в этом коде, однако побочные эффекты setter могут привести к уничтожению объекта, если он действительно перестал иметь сильных владельцев.

  1. Почему дополнительная сильная временная ссылка не всегда нужна для защиты объекта?

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