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, 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("Перший defer") }
    defer { print("Другий defer") }
    defer { print("Третій defer") }

    print("Тіло функції")
}
// Вивід:
// Тіло функції
// Третій defer
// Другий defer
// Перший 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() буде викликано навіть при throw або 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) та logAudit(user) будуть викликані
    // при будь-якому throw або 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("defer do-блоку") }
        print("inside do")
    }
    // "defer do-блоку" виводиться тут
    print("after do")
}
    // Вивід: start, inside do, defer do-блоку, after do

for i in 1...3 {
    defer { print("end iteration \(i)") }
    print("iteration \(i)")
}
    // Вивід: 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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

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