defer — керуюча конструкція в Swift, яка планує виконання блоку коду на момент виходу з поточної області видимості (scope). Блок defer виконується незалежно від способу завершення — return, break, throw, fatalError або нормальне завершення. За даними Swift Language Guide (2025), за наявності кількох defer в одному scope вони виконуються у зворотному порядку оголошення — останній оголошений виконується першим (LIFO). Це робить defer незамінним для гарантованого очищення ресурсів: закриття файлових дескрипторів, зняття блокувань, звільнення тимчасових покажчиків без ризику пропустити cleanup при достроковому виході.
Головне
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 }. Такий патерн гарантує, що звільнення ресурсу не буде пропущено за жодних обставин.
Коли в одному scope оголошено кілька defer, вони виконуються у зворотному порядку оголошення (LIFO — Last In, First Out). Це означає, що останній оголошений defer виконається першим, а перший — останнім:
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 — гарантоване очищення ресурсів. Розглянемо роботу з файловою системою. Відкриття файлу через FileHandle вимагає явного закриття — defer гарантує, що close буде викликано за будь-якого сценарію:
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 ефективно поєднується з throws-функціями. Коли функція може викинути помилку на будь-якому етапі, defer гарантує cleanup без дублювання коду в кожному catch-блоці або guard-ранньому виході:
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-циклу — при кожній ітерації:
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 відобразяться на його виконанні.
Перша помилка — припущення про порядок виконання, відмінний від 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, що відкладає виконання блоку до виходу з поточної області видимості. Блок виконується завжди — при return, throw, break або нормальному завершенні. Використовується для гарантованого очищення ресурсів: закриття файлів, зняття блокувань.
У зворотному порядку оголошення (LIFO) — останній оголошений defer виконується першим. Це гарантує коректний cleanup вкладених ресурсів: якщо ресурс B відкрито після A, він буде закритий до A, що запобігає залежностям від уже звільнених ресурсів.
Не напряму — Swift забороняє поширення помилки з defer. Використовуйте try? або try! для операцій, які можуть викинути помилку. Найкраща практика — робити cleanup-функції non-throwing або обробляти помилки всередині defer без пробросу назовні.
defer прив'язаний до scope і виконується при будь-якому виході, включаючи return, throw та break. finally (в інших мовах) прив'язаний до try-catch і виконується тільки при наявності try. У Swift немає finally — defer повністю покриває цей сценарій і працює для будь-якого scope, а не тільки для обробки помилок.
Так, defer читає змінні на момент виходу з scope, а не на момент оголошення. Якщо змінна змінюється після оголошення defer, блок defer побачить останнє значення. Це відрізняється від звичайних замикань, де захоплення фіксується при створенні.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також