Разберите, в какой момент выполняется defer в теле цикла и какой будет вывод программы: пример с кодом

Разберите, в какой момент выполняется defer в теле цикла и какой будет вывод программы:

for index in 1...2 {
    defer { print("cleanup \(index)") }
    print("work \(index)")
}
print("finished")
Проходите собеседования с ИИ помощником Hintsage

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

defer выполняется при выходе из области видимости тела текущей итерации. Поэтому вывод будет таким:

work 1 cleanup 1 work 2 cleanup 2 finished

Каждая итерация цикла создаёт собственную область выполнения тела цикла. Зарегистрированный в ней defer срабатывает до перехода к следующей итерации.

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

Механизм defer нужен для структурированного освобождения ресурсов и восстановления состояния независимо от способа выхода из области: обычного завершения, return, throw, break или continue.

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

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

Важно отличать область видимости defer от функции целиком. Если поместить defer внутрь тела цикла, очистка произойдёт после каждой итерации; если поместить его вокруг всего цикла, она задержится до выхода из внешней области.

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

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

defer регистрирует замыкание в текущей области видимости, но не выполняет его сразу. Когда выполнение покидает эту область, Swift запускает отложенное действие.

В данном коде область тела цикла завершается после выполнения print("work \(index)"). Поэтому сначала выполняется очистка первой итерации, затем начинается вторая. После второй очистки цикл завершается, и выполняется print("finished").

Если в теле итерации произойдут return, throw, break или continue, defer этой области также выполнится перед выходом. Несколько defer в одной области выполняются в обратном порядке регистрации, но это правило не меняет границу области: defer цикла не откладывается до завершения всей функции.

Практический компромисс таков: defer делает очистку надёжной и локальной, но его нужно размещать на уровне фактического владения ресурсом. Слишком внешняя область увеличивает время удержания ресурса, слишком внутренняя может освободить его раньше, чем это допускает логика операции.

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

Допустим, цикл последовательно обрабатывает изображения, и для каждого изображения создаётся временный буфер. Вариант с defer вокруг всего цикла удобен, но удерживает все буферы до окончания обработки. Явный вызов очистки в нескольких ветках быстрее освобождает память, однако легко пропустить ветку с ошибкой.

Выбранное решение — создавать ресурс внутри тела итерации и регистрировать defer сразу после успешного создания:

func processItems() { for item in 1...2 { var buffer = "buffer \(item)" defer { buffer.removeAll() } print("processing \(buffer)") } } processItems()

Так ресурс очищается после каждой итерации, в том числе при ошибочном выходе из неё. Результат — ограниченное время жизни временных данных без дублирования cleanup-кода.

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

  1. Сработает ли defer при continue?

    Да. continue покидает текущую итерацию, поэтому перед переходом к следующей итерации выполняются все defer, зарегистрированные в теле цикла. Это позволяет безопасно возвращать состояние или освобождать временный ресурс даже в ветке досрочного пропуска.

  2. Что произойдёт с defer, объявленным внутри условного блока?

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

  3. Можно ли использовать defer для управления порядком нескольких ресурсов?

    Да, но ресурсы должны регистрироваться в порядке, обратном желаемому порядку освобождения. Поскольку defer выполняются по принципу LIFO, последний зарегистрированный ресурс будет освобождён первым. Это соответствует типичному правилу: зависимый ресурс закрывается раньше ресурса, от которого он зависит.