Проверьте, доживёт ли локальный объект до выполнения defer, если функция уже выполнила return.
Да. Если тело defer обращается к локальному объекту, Swift должен сохранить этот объект доступным до выполнения отложенного блока. defer выполняется перед фактическим выходом из функции, поэтому объект не может быть уничтожен раньше этого обращения.
defer появился как средство гарантировать освобождение ресурсов при любом пути выхода из функции: обычном return, ошибке или исключении. Он решает проблему дублирования завершающего кода во всех ветвях выполнения.
В связке с ARC это означает, что обращение к объекту из defer учитывается при анализе его времени жизни. ARC не освобождает объект до момента, когда такой доступ больше не может произойти.
Фактический выход из функции происходит не сразу после выполнения return: сначала выполняются все зарегистрированные блоки defer. Если объект нужен одному из них, преждевременное освобождение привело бы к обращению к недействительной ссылке.
Неверный вывод состоит в том, что любая локальная переменная автоматически живёт до конца функции. ARC может завершить её жизнь раньше последнего машинно необходимого использования, но defer, использующий объект, становится таким использованием при выходе.
Блоки defer выполняются при покидании текущей области видимости. Если таких блоков несколько, они выполняются в обратном порядке регистрации — по принципу LIFO.
Когда defer содержит обращение к локальному объекту, компилятор организует его хранение так, чтобы объект оставался доступным до выполнения этого обращения. После завершения defer, если других сильных владельцев нет, ARC может уничтожить объект.
В этом примере return не указан явно: достижение конца функции также является выходом из области. Сначала выполняется defer, затем функция завершается, и только после этого объект становится кандидатом на уничтожение.
Важно отличать это от простого наличия defer. Если отложенный блок не использует объект, сам факт регистрации defer не обязан продлевать время жизни объекта. Также наличие других сильных ссылок может отложить deinit ещё дольше.
В сетевом клиенте нужно гарантированно закрыть временный ресурс независимо от результата операции. Вариант с ручным вызовом close перед каждым return приводит к пропущенным веткам и дублированию кода.
Вариант с отдельной сильной ссылкой, которую освобождают вручную, хуже согласуется с ARC: ручное управление не даёт нужной гарантии и усложняет обработку ошибок. Выбранный вариант — defer, обращающийся к ресурсу: он централизует завершение операции, выполняется при любом выходе и одновременно гарантирует доступность ресурса до закрытия.
После выполнения defer ресурс освобождается ARC, если его никто больше не удерживает. Это уменьшает риск утечки и делает порядок завершения очевидным.
defer после return или вместо него?return вычисляет возвращаемое значение и инициирует выход, но перед фактическим выходом выполняются отложенные блоки. Поэтому defer не отменяет return, а предваряет завершение функции. Если в defer изменить локальную переменную, это обычно не изменит уже вычисленное возвращаемое значение.
defer жизнь объекта, если в нём вызывается только метод без сохранения результата?Да, на время выполнения вызова объект должен оставаться валидным. После завершения метода продление заканчивается, если других сильных ссылок нет. Само наличие вызова не гарантирует сохранение объекта после окончания defer.
defer работает с текущим состоянием локальной переменной на момент выполнения блока. Если сильная переменная была переназначена или получила nil, отложенный код увидит это состояние; прежний объект может стать доступным для уничтожения уже при переназначении, если других владельцев нет. Поэтому для гарантированного закрытия конкретного ресурса нельзя полагаться на изменяемую ссылку без учёта её последующих изменений.