defer es una construcción de control de flujo en Swift que programa la ejecución de un bloque de código al salir del ámbito actual. El bloque defer se ejecuta independientemente de cómo termine el ámbito — return, break, throw, fatalError o finalización normal. Según la Guía de Lenguaje Swift (2025), cuando hay múltiples defer en un mismo ámbito, se ejecutan en orden inverso al de declaración — el último declarado se ejecuta primero (LIFO). Esto hace que defer sea indispensable para la limpieza garantizada de recursos: cerrar descriptores de archivo, liberar bloqueos, liberar punteros temporales sin riesgo de omitir la limpieza en una salida anticipada.
Puntos Clave
defer es una construcción de control de flujo en Swift, introducida en Swift 2.0 (2015), que pospone la ejecución de su bloque hasta que el ámbito actual termina. La característica clave: defer garantiza que su cuerpo se ejecute independientemente de cómo termine el ámbito — con éxito (return), con error (throw), prematuramente (break, continue) o fatalmente (fatalError, precondition).
Sintácticamente, defer se escribe como defer { /* código */ } y puede colocarse en cualquier lugar dentro del ámbito. El compilador de Swift garantiza que el código dentro de defer se ejecutará incluso si ocurre una excepción o return entre la declaración del defer y el final del ámbito. Esto distingue fundamentalmente a defer del código normal colocado al final de una función, que podría omitirse en una salida anticipada.
Según un artículo de Chris Lattner (Creador de Swift, 2015), defer se inspiró en construcciones similares de otros lenguajes — defer en Go, finally en Java/Python, scope guard en C++ — pero con una diferencia importante: en Swift, defer se ejecuta al final del ámbito, no inmediatamente después de un bloque try-catch. Esto proporciona un comportamiento más predecible para la limpieza en funciones con múltiples puntos de salida.
Use defer para la gestión simétrica de recursos: abrir archivo → defer { close }, adquirir bloqueo → defer { unlock }. Este patrón garantiza que la liberación de recursos nunca se omita bajo ninguna circunstancia.
Cuando se declaran múltiples defer en el mismo ámbito, se ejecutan en orden inverso al de declaración (LIFO — Last In, First Out). Esto significa que el último defer declarado se ejecuta primero, y el primero se ejecuta último:
func exampleDeferOrder() {
defer { print("Primer defer") }
defer { print("Segundo defer") }
defer { print("Tercer defer") }
print("Cuerpo de la función")
}
// Salida:
// Cuerpo de la función
// Tercer defer
// Segundo defer
// Primer defer
El orden LIFO es importante para la gestión correcta de recursos anidados. Si el archivo A se abre primero y luego el archivo B, deben liberarse en orden inverso: primero B, luego A. Con defer, esto ocurre automáticamente — declare un defer justo después de abrir cada recurso, y el orden de limpieza será correcto independientemente del número de puntos de salida de la función.
Según Swift by Sundell (2024), esta característica hace que defer sea ideal para bloqueos anidados y transacciones: adquirir bloqueo → defer { unlock } → adquirir siguiente → defer { unlock }. LIFO garantiza que los bloqueos se liberen en orden inverso al de adquisición, evitando interbloqueos.
El caso de uso principal de defer es la limpieza garantizada de recursos. Considere trabajar con el sistema de archivos. Abrir un archivo mediante FileHandle requiere un cierre explícito — defer garantiza que se llame a close en cualquier escenario:
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() se llamará incluso en throw o return
}
Otro escenario típico son las animaciones de UI con un indicador de carga. Antes de iniciar una carga, establezca el indicador isLoading = true, y defer lo vuelve a false al salir de la función, independientemente del éxito o fracaso de la solicitud. Esto evita que el indicador permanezca en true debido a un error no manejado, bloqueando la interfaz para siempre.
Según el Bitbucket Engineering Blog (2024), defer también se usa para perfilado: registre el tiempo al inicio de una función, y en defer — calcule y muestre la diferencia. Esto proporciona mediciones precisas de rendimiento para todas las rutas de ejecución, incluidas las erróneas.
defer funciona eficazmente con funciones throws. Cuando una función puede lanzar un error en cualquier etapa, defer garantiza la limpieza sin duplicar código en cada bloque catch o salida temprana con 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) y logAudit(user) serán llamados
// en cualquier throw o return
}
Importante: defer se ejecuta antes de que el control se transfiera fuera del bloque catch, pero después de que ocurra el error. Si se lanza un error dentro de defer, Swift no permite usar try directamente dentro de defer — necesita try? o try!. Según la Documentación de Apple, Swift no permite que un error escape de defer, ya que eso violaría la garantía de ejecución del bloque.
Coloque defer inmediatamente después de la adquisición del recurso. Esto sigue el principio de proximidad: el lector ve la adquisición y la liberación juntas, lo que mejora la fiabilidad del código y simplifica la revisión del código.
defer se ejecuta al salir del ámbito en el que se declara. Si defer se declara dentro de un bloque do, se ejecuta al salir de ese bloque, no de la función externa. Si está dentro de un bucle for — en cada iteración:
func scopeExample() {
print("start")
do {
defer { print("defer del bloque do") }
print("inside do")
}
// "defer del bloque do" se imprime aquí
print("after do")
}
// Salida: start, inside do, defer del bloque do, after do
for i in 1...3 {
defer { print("end iteration \(i)") }
print("iteration \(i)")
}
// Salida: iteration 1, end iteration 1, iteration 2, end iteration 2, ...
Las variables capturadas por defer se leen en el momento de salida del ámbito, no en el momento de la declaración del defer. Si una variable cambia entre la declaración del defer y el final del ámbito, defer verá el último valor. Esta es una distinción importante respecto a los cierres, donde la captura ocurre en el momento de creación. Tenga cuidado: los cambios en una variable después de declarar defer afectarán su ejecución.
El primer error — suponer un orden de ejecución distinto de LIFO. Si el orden de limpieza importa y los defer se declaran en el orden incorrecto, los recursos pueden liberarse violando dependencias. Solución: declare defer inmediatamente después de capturar cada recurso. Segundo recurso abierto → defer { close second } antes de que se cierre el primero.
El segundo error — usar defer para lógica no relacionada con la limpieza. defer está diseñado para la limpieza garantizada, no para el control de flujo principal. Si el código dentro de defer afecta el valor de retorno, casi siempre es un error. defer no puede cambiar el valor de retorno de una función (a diferencia de Java finally, donde return en finally sobrescribe el return original).
El tercer error — lanzar un error desde defer. Swift prohíbe try dentro de defer si el error podría propagarse hacia afuera. Use try? o try! para operaciones que puedan lanzar un error, o envuélvalas en una función separada sin throws. Según O’Reilly “Swift in Depth” (2025), una buena práctica es hacer que las funciones de limpieza no lancen excepciones (non-throwing) o manejar los errores dentro de defer.
Preguntas Frecuentes
defer es una construcción de Swift que pospone la ejecución de un bloque hasta que el ámbito actual termina. El bloque siempre se ejecuta — en return, throw, break o finalización normal. Se usa para la limpieza garantizada de recursos: cerrar archivos, liberar bloqueos.
En orden inverso de declaración (LIFO) — el último defer declarado se ejecuta primero. Esto garantiza una limpieza correcta de recursos anidados: si el recurso B se abre después de A, se cerrará antes que A, evitando dependencias de recursos ya liberados.
No directamente — Swift impide la propagación de errores desde defer. Use try? o try! para operaciones que puedan lanzar un error. La mejor práctica es hacer que las funciones de limpieza no lancen excepciones o manejar los errores dentro de defer sin propagarlos hacia afuera.
defer está vinculado a un ámbito y se ejecuta en cualquier salida, incluyendo return, throw y break. finally (en otros lenguajes) está vinculado a try-catch y solo se ejecuta cuando hay try. Swift no tiene finally — defer cubre completamente este escenario y funciona para cualquier ámbito, no solo para el manejo de errores.
Sí, defer lee las variables en el momento de salida del ámbito, no en el momento de la declaración. Si una variable cambia después de declarar defer, el bloque defer verá el último valor. Esto difiere de los cierres normales, donde la captura se fija en el momento de creación.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también