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 је важан за коректно управљање угњежденим ресурсима. Ако се прво отвара датотека А, затим датотека Б, потребно их је ослободити обрнутим редоследом: прво Б, па А. Са 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)")
}
// Резултат: итерација 1, крај итерације 1, итерација 2, крај итерације 2, ...
Променљиве које је defer захватио читају се у тренутку изласка из scope-а, а не у тренутку декларисања defer-а. Ако се променљива мења између декларисања defer-а и краја scope-а, defer ће видети последњу вредност. Ово је важна разлика у односу на затворења (closures), где се захватање дешава у тренутку креирања. Будите пажљиви: промене променљиве након декларисања 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 угњеждених ресурса: ако је ресурс Б отворен после А, биће затворен пре А, што спречава зависности од већ ослобођених ресурса.
Не директно — 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. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође