defer — egy vezérlési szerkezet Swiftben, amely egy kódblokk végrehajtását az aktuális hatókörből (scope) való kilépés pillanatára ütemezi. A defer blokk a befejezés módjától függetlenül végrehajtódik — return, break, throw, fatalError vagy normál befejezés. A Swift Language Guide (2025) szerint, ha több defer van egy scope-ban, akkor a deklarálás fordított sorrendjében hajtódnak végre — az utoljára deklarált hajtódik végre elsőként (LIFO). Ez teszi a defer-t nélkülözhetetlenné a garantált erőforrás-tisztításhoz: fájlleírók bezárása, zárak feloldása, ideiglenes mutatók felszabadítása anélkül, hogy a cleanup-ot kihagynánk idő előtti kilépéskor.
Főbb pontok
defer — a Swift nyelv egy vezérlési szerkezete, amelyet a Swift 2.0-ban (2015) vezettek be, és amely a blokkja végrehajtását az aktuális scope befejeződésének pillanatáig késlelteti. Kulcsjellemző: a defer garantálja a törzse végrehajtását függetlenül attól, hogy a scope pontosan hogyan fejeződik be — sikeresen (return), hibával (throw), idő előtt (break, continue) vagy végzetesen (fatalError, precondition).
Szintaktikailag a defer így néz ki: defer { /* kód */ }, és a scope-on belül bárhol elhelyezhető. A Swift fordító garantálja, hogy a defer belsejében lévő kód végrehajtódik, még akkor is, ha a defer deklarálása és a scope vége között kivétel vagy return történik. Ez alapvetően különbözteti meg a defer-t a függvény végére helyezett hétköznapi kódtól, amely idő előtti kilépéskor kimaradhat.
Chris Lattner (a Swift megalkotója, 2015) cikke szerint a defer más nyelvek hasonló szerkezeteiből merített ihletet — defer a Go-ban, finally a Java/Python-ban, scope guard a C++-ban — de egy fontos különbséggel: Swiftben a defer a scope végén hajtódik végre, nem közvetlenül a try-catch blokk után. Ez kiszámíthatóbb viselkedést biztosít a cleanup számára a több kilépési ponttal rendelkező függvényekben.
Használja a defer-t az erőforrások szimmetrikus kezeléséhez: fájl megnyitása → defer { close }, zár beállítása → defer { unlock }. Az ilyen minta garantálja, hogy az erőforrás felszabadítása semmilyen körülmények között nem marad ki.
Amikor egy scope-ban több defer van deklarálva, azok a deklarálás fordított sorrendjében hajtódnak végre (LIFO — Last In, First Out). Ez azt jelenti, hogy az utoljára deklarált defer hajtódik végre elsőként, az első pedig utoljára:
func exampleDeferOrder() {
defer { print("Első defer") }
defer { print("Második defer") }
defer { print("Harmadik defer") }
print("Függvény törzse")
}
// Kimenet:
// Függvény törzse
// Harmadik defer
// Második defer
// Első defer
A LIFO sorrend fontos a beágyazott erőforrások helyes kezeléséhez. Ha először az A fájl nyílik meg, majd a B fájl, akkor ezeket fordított sorrendben kell felszabadítani: először B-t, majd A-t. A defer-rel ez automatikusan megtörténik — deklarálja a defer-t közvetlenül minden erőforrás megnyitása után, és a tisztítási sorrend helyes lesz, függetlenül a függvényből való kilépési pontok számától.
A Swift by Sundell (2024) szerint ez a tulajdonság teszi a defer-t ideálissá beágyazott zárakhoz és tranzakciókhoz: zár megszerzése → defer { unlock } → következő megszerzése → defer { unlock }. A LIFO garantálja, hogy a zárak a megszerzés fordított sorrendjében szabadulnak fel, megelőzve a holtpontokat.
A defer fő alkalmazása — garantált erőforrás-tisztítás Tekintsük a fájlrendszerrel való munkát. Fájl megnyitása a FileHandle segítségével explicit bezárást igényel — a defer garantálja, hogy a close minden forgatókönyvben meghívásra kerül:
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) ?? ""
// A handle.close() még throw vagy return esetén is meghívásra kerül
}
Egy másik tipikus forgatókönyv — UI-animációk betöltési zászlóval. A betöltés megkezdése előtt a isLoading = true zászló beállításra kerül, a defer pedig false-ra változtatja a függvényből való kilépéskor, függetlenül a kérés sikerétől vagy hibájától. Ez megakadályozza azt a helyzetet, amikor a zászló egy feldolgozatlan hiba miatt true marad és örökre blokkolja a felületet.
A Bitbucket Engineering Blog (2024) szerint a defer-t profilozásra is használják: a függvény elején rögzíthető az idő, a defer-ben pedig kiszámítható és kiírható a különbség. Ez pontos teljesítményméréseket ad az összes végrehajtási útvonalra, beleértve a hibásakat is.
defer hatékonyan kombinálható a throws-függvényekkel. Amikor egy függvény bármely szakaszban hibát dobhat, a defer garantálja a cleanup-ot anélkül, hogy a kódot meg kellene ismételni minden catch blokkban vagy guard korai kilépésben:
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)
// A closeDatabase(db) és logAudit(user) meghívásra kerül
// minden throw vagy return esetén
}
Fontos: a defer a catch blokkból történő vezérlésátadás előtt, de a hiba bekövetkezése után hajtódik végre. Ha a defer-ben hiba dobódik, a Swift nem engedélyezi a try közvetlen használatát a defer-en belül — try? vagy try! szükséges. Az Apple Documentation szerint a Swift nem engedi, hogy a hiba „kijöjjön" a defer-ből, mert ez megsértené a blokk végrehajtási garanciáját.
Helyezze a defer-t közvetlenül az erőforrás megszerzése után. Ez a közelség elvét követi: az olvasó a megszerzést és a felszabadítást egymás mellett látja, ami növeli a kód megbízhatóságát és egyszerűsíti a kódellenőrzést.
defer annak a scope-nak az elhagyásakor hajtódik végre, amelyben deklarálták. Ha a defer egy do-blokkon belül van deklarálva, akkor a blokk elhagyásakor hajtódik végre, nem a külső függvényből való kilépéskor. Ha egy for-cikluson belül van — minden iterációnál:
func scopeExample() {
print("start")
do {
defer { print("do-blokk defer") }
print("inside do")
}
// "do-blokk defer" itt nyomtat
print("after do")
}
// Kimenet: start, inside do, do-blokk defer, after do
for i in 1...3 {
defer { print("end iteration \(i)") }
print("iteration \(i)")
}
// Kimenet: iteráció 1, iteráció vége 1, iteráció 2, iteráció vége 2, ...
A defer által rögzített változók a scope elhagyásának pillanatában kerülnek beolvasásra, nem a defer deklarálásának pillanatában. Ha egy változó megváltozik a defer deklarálása és a scope vége között, a defer az utolsó értéket fogja látni. Ez fontos különbség a lezárásokhoz (closures) képest, ahol a rögzítés a létrehozás pillanatában történik. Legyen óvatos: a változó defer deklarálása utáni módosításai befolyásolják a defer végrehajtását.
Első hiba — feltételezés a LIFO-tól eltérő végrehajtási sorrendről. Ha a cleanup sorrendje fontos, és a defer-ek rossz sorrendben lettek deklarálva, az erőforrások függőségsértéssel szabadulhatnak fel. Megoldás: deklarálja a defer-t közvetlenül minden erőforrás megszerzése után. Második erőforrás megnyitva → defer { close second } mielőtt az első bezárásra kerülne.
Második hiba — a defer használata a tisztítással nem összefüggő logikához. A defer garantált cleanup-ra lett tervezve, nem a fő vezérlési folyamathoz. Ha a defer-ben lévő kód befolyásolja a visszatérési értéket, az szinte mindig hiba. A defer nem tudja megváltoztatni a függvény return értékét (ellentétben a Java finally-jel, ahol a finally-ben lévő return felülírja az eredeti return-t).
Harmadik hiba — hiba dobása a defer-ből. A Swift tiltja a try-t a defer-en belül, ha a hiba kifelé terjedhet. Használjon try? vagy try! a műveletekhez, amelyek hibát dobhatnak, vagy csomagolja be őket egy külön, throws nélküli függvénybe. Az O'Reilly „Swift in Depth" (2025) szerint jó gyakorlat a cleanup függvényeket nem dobóvá (non-throwing) tenni, vagy a hibákat a defer-en belül kezelni.
Gyakran Ismételt Kérdések
defer — egy Swift vezérlési szerkezet, amely késlelteti a blokk végrehajtását az aktuális hatókör elhagyásáig. A blokk mindig végrehajtódik — return, throw, break vagy normál befejezéskor. Garantált erőforrás-tisztításhoz használják: fájlok bezárása, zárak feloldása.
A deklarálás fordított sorrendjében (LIFO) — az utoljára deklarált defer hajtódik végre elsőként. Ez garantálja a beágyazott erőforrások helyes cleanup-ját: ha B erőforrás A után nyílt meg, akkor A előtt bezárul, megelőzve a már felszabadított erőforrásoktól való függőséget.
Nem közvetlenül — a Swift tiltja a hiba terjedését a defer-ből. Használjon try? vagy try! a hibát dobható műveletekhez. A legjobb gyakorlat a cleanup függvények non-throwing módon történő megvalósítása, vagy a hibák kezelése a defer-en belül, kifelé terjedés nélkül.
defer a scope-hoz van kötve és minden kilépéskor végrehajtódik, beleértve a return, throw és break eseteket. A finally (más nyelvekben) a try-catch-hez van kötve és csak try esetén hajtódik végre. Swiftben nincs finally — a defer teljes mértékben lefedi ezt a forgatókönyvet és bármilyen scope-ra működik, nem csak a hibakezelésre.
Igen, a defer a változókat a scope elhagyásának pillanatában olvassa, nem a deklarálás pillanatában. Ha egy változó a defer deklarálása után megváltozik, a defer blokk az utolsó értéket fogja látni. Ez eltér a hétköznapi lezárásoktól, ahol a rögzítés a létrehozás pillanatában rögzül.
Összefoglalás
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is