defer — что это, синтаксис и порядок выполнения

Автор: IT Sectr Опубликовано: 2026-06-20 Время чтения: 8 мин

defer — управляющая конструкция в Swift, которая планирует выполнение блока кода на момент выхода из текущей области видимости (scope). Блок defer выполняется независимо от способа завершения — return, break, throw, fatalError или нормальное завершение. По данным Swift Language Guide (2025), при наличии нескольких defer в одном scope они выполняются в обратном порядке объявления — последний объявленный выполняется первым (LIFO). Это делает defer незаменимым для гарантированной очистки ресурсов: закрытия файловых дескрипторов, снятия блокировок, освобождения временных указателей без риска пропустить cleanup при досрочном выходе.

Главное

  • defer — блок кода, выполняемый при выходе из области видимости, независимо от причины выхода (return, throw, break)
  • Порядок LIFO: несколько defer выполняются снизу вверх — последний объявленный срабатывает первым
  • Очистка ресурсов — основное применение: закрытие файлов, снятие блокировок, завершение анимаций
  • Определение переменных: defer видит переменные на момент выхода, а не на момент объявления
  • Не заменяет do-catch для обработки ошибок — defer отвечает за cleanup, а не за контроль потока

Что такое defer?

defer — это управляющая конструкция языка Swift, введённая в Swift 2.0 (2015), которая откладывает выполнение своего блока до момента, когда текущий scope завершается. Ключевая особенность: defer гарантирует выполнение своего тела независимо от того, как именно завершается scope — успешно (return), с ошибкой (throw), преждевременно (break, continue) или фатально (fatalError, precondition).

Синтаксически defer выглядит как defer { /* код */ } и может располагаться в любом месте внутри scope. Компилятор Swift гарантирует, что код внутри defer будет выполнен, даже если между объявлением defer и концом scope произойдёт исключение или return. Это принципиально отличает defer от обычного кода, размещённого в конце функции, который может быть пропущен при досрочном выходе.

По данным статьи Криса Латтнера (Swift Creator, 2015), defer был вдохновлён аналогичными конструкциями в других языках — defer в Go, finally в Java/Python, scope guard в C++ — но с важным отличием: в Swift defer выполняется в конце scope, а не сразу после try-catch-блока. Это даёт более предсказуемое поведение для cleanup в функциях с множественными точками выхода.

Используйте defer для симметричного управления ресурсами: открытие файла → defer { close }, установка блокировки → defer { unlock }. Такой паттерн гарантирует, что освобождение ресурса не будет пропущено ни при каких обстоятельствах.

Порядок выполнения нескольких defer

Когда в одном scope объявлено несколько defer, они выполняются в обратном порядке объявления (LIFO — Last In, First Out). Это означает, что последний объявленный defer выполнится первым, а первый — последним:

swift
func exampleDeferOrder() {
    defer { print("First defer") }
    defer { print("Second defer") }
    defer { print("Third defer") }

    print("Function body")
}
// Output:
// Function body
// Third defer
// Second defer
// First defer

Порядок LIFO важен для корректного управления вложенными ресурсами. Если сначала открывается файл A, затем файл B, освобождать их нужно в обратном порядке: сначала B, потом A. С defer это происходит автоматически — объявите defer сразу после открытия каждого ресурса, и порядок очистки будет правильным независимо от числа точек выхода из функции.

По данным Swift by Sundell (2024), эта особенность делает defer идеальным для вложенных блокировок и транзакций: захват блокировки → defer { unlock } → захват следующей → defer { unlock }. LIFO гарантирует, что блокировки освобождаются в порядке, обратном захвату, предотвращая дедлоки.

Defer для очистки ресурсов

Основное применение defer — гарантированная очистка ресурсов. Рассмотрим работу с файловой системой. Открытие файла через FileHandle требует явного закрытия — defer гарантирует, что close будет вызван при любом сценарии:

swift
func readFile(path: String) throws -> String {
    let handle = try FileHandle(forReadingFrom: URL(fileURLWithPath: path))
    defer { try? handle.close() }

    let data = try handle.readToEnd()
    guard let data else { throw FileError.empty() }

    return String(data: data, encoding: .utf8) ?? ""
    // handle.close() will be called even on throw or return
}

Другой типичный сценарий — UI-анимации с флагом загрузки. Перед началом загрузки устанавливается флаг isLoading = true, а defer меняет его на false при выходе из функции, независимо от успеха или ошибки запроса. Это предотвращает ситуацию, когда флаг остаётся true из-за необработанной ошибки и блокирует интерфейс навсегда.

По данным Bitbucket Engineering Blog (2024), defer также применяется для профилирования: в начале функции можно записать время, а в defer — вычислить и вывести разницу. Это даёт точные замеры производительности всех путей выполнения, включая ошибочные.

Defer и обработка ошибок

defer эффективно сочетается с throws-функциями. Когда функция может выбросить ошибку на любом этапе, defer гарантирует cleanup без дублирования кода в каждом catch-блоке или guard-раннем выходе:

swift
func processTransaction() throws {
    let db = try openDatabase()
    defer { closeDatabase(db) }

    let user = try fetchUser(from: db)
    defer { logAudit(user) }

    let result = try performPayment(user)
    sendNotification(result)
    // closeDatabase(db) and logAudit(user) will be called
    // on any throw or return
}

Важно: defer выполняется до передачи управления из catch-блока, но после возникновения ошибки. Если в defer выброшена ошибка, Swift не разрешает использовать try внутри defer напрямую — требуется try? или try!. По данным Apple Documentation, Swift не позволяет ошибке «вылететь» из defer, поскольку это нарушило бы гарантию выполнения блока.

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

Правила области видимости

defer выполняется при выходе из scope, в котором он объявлен. Если defer объявлен внутри do-блока, он выполняется при выходе из этого блока, а не из внешней функции. Если внутри for-цикла — при каждой итерации:

swift
func scopeExample() {
    print("start")
    do {
        defer { print("do-block defer") }
        print("inside do")
    }
    // "do-block defer" prints here
    print("after do")
}
    // Output: start, inside do, do-block defer, after do

for i in 1...3 {
    defer { print("end iteration \(i)") }
    print("iteration \(i)")
}
    // Output: iteration 1, end iteration 1, iteration 2, end iteration 2, ...

Переменные, захваченные defer, читаются на момент выхода из scope, а не на момент объявления defer. Если переменная изменяется между объявлением defer и концом scope, defer увидит последнее значение. Это важное отличие от замыканий, где захват происходит на момент создания. Будьте внимательны: изменения переменной после объявления defer отразятся на его выполнении.

Типовые ошибки с defer

Первая ошибка — предположение о порядке выполнения, отличном от LIFO. Если порядок cleanup важен, а defer объявлены в неправильном порядке, ресурсы могут освобождаться с нарушением зависимости. Решение: объявляйте defer сразу после захвата каждого ресурса. Второй ресурс открыт → defer { close second } до того, как закрыт первый.

Вторая ошибка — использование defer для логики, не связанной с очисткой. defer предназначен для гарантированного cleanup, а не для основного потока управления. Если код в defer влияет на возвращаемое значение, это почти всегда ошибка. defer не может изменить return-значение функции (в отличие от Java finally, где return в finally перезаписывает исходный return).

Третья ошибка — выброс ошибки из defer. Swift запрещает try внутри defer, если ошибка может распространиться наружу. Используйте try? или try! для операций, которые могут выбросить ошибку, или оборачивайте их в отдельную функцию без throws. По данным O'Reilly «Swift in Depth» (2025), хорошая практика — сделать cleanup-функции небросающими (non-throwing) или обрабатывать ошибки внутри defer.

Часто задаваемые вопросы

Что такое defer в Swift?

defer — управляющая конструкция Swift, откладывающая выполнение блока до выхода из текущей области видимости. Блок выполняется всегда — при return, throw, break или нормальном завершении. Используется для гарантированной очистки ресурсов: закрытия файлов, снятия блокировок.

В каком порядке выполняются несколько defer?

В обратном порядке объявления (LIFO) — последний объявленный defer выполняется первым. Это гарантирует корректный cleanup вложенных ресурсов: если ресурс B открыт после A, он будет закрыт до A, что предотвращает зависимости от уже освобождённых ресурсов.

Можно ли выбросить ошибку из defer?

Не напрямую — Swift запрещает распространение ошибки из defer. Используйте try? или try! для операций, которые могут выбросить ошибку. Лучшая практика — делать cleanup-функции non-throwing или обрабатывать ошибки внутри defer без проброса наружу.

В чём разница между defer и do-catch-finally?

defer привязан к scope и выполняется при любом выходе, включая return, throw и break. finally (в других языках) привязан к try-catch и выполняется только при наличии try. В Swift нет finally — defer полностью покрывает этот сценарий и работает для любого scope, а не только для обработки ошибок.

Видит ли defer изменения переменных после своего объявления?

Да, defer читает переменные на момент выхода из scope, а не на момент объявления. Если переменная изменяется после объявления defer, блок defer увидит последнее значение. Это отличается от обычных замыканий, где захват фиксируется при создании.

Итоги

  • defer — блок финализации, выполняемый при выходе из scope независимо от причины (return, throw, break, нормальное завершение)
  • LIFO порядок — несколько defer выполняются снизу вверх, последний объявленный — первый выполненный
  • Очистка ресурсов — основное применение: закрытие файлов, снятие блокировок, освобождение указателей, остановка анимаций
  • Совместимость с throws: defer выполняется после ошибки, но до выхода из catch-блока; ошибка из defer не распространяется
  • Scope: defer выполняется при выходе из того scope, где объявлен — do-блок, цикл, функция
  • Захват переменных: defer читает значения на момент выхода, а не на момент объявления — будьте внимательны с мутациями
  • Лучшая практика: объявляйте defer сразу после захвата ресурса, не используйте defer для бизнес-логики, делайте cleanup-функции non-throwing

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также