ProgramaciónDesarrollador iOS/Móvil

¿Cómo funcionan los closures en Swift, en qué se diferencian de las funciones y qué aspectos de gestión de memoria están relacionados con su uso?

Supere entrevistas con el asistente de IA Hintsage

Respuesta.

Historia de la pregunta:

Los closures como concepto provienen de lenguajes funcionales y permiten pasar bloques de código como valores. En Swift, los closures (análogos a las lambdas de otros lenguajes) aparecieron desde el inicio. Gracias a esto, se pueden implementar elegantemente llamadas asíncronas, delegar acciones, ordenar colecciones, filtrar, etc.

Problema:

Los closures capturan variables del contexto circundante. Si entre estas variables hay referencias a objetos de clase, puede surgir un retain cycle, especialmente si el closure se almacena como propiedad de esa clase. También es importante entender la diferencia entre closures escaping y non-escaping, y darse cuenta de cómo funciona la captura de valores.

Solución:

Swift implementa closures como objetos independientes, pueden capturar el contexto y el dueño del closure debe tener cuidado con la arquitectura.

Ejemplo de código:

class Performer { var onComplete: (() -> Void)? func doWork() { onComplete = { print("¡Trabajo completado!") } } } // Captura self dentro del closure class Test { var value = 0 func start() { DispatchQueue.global().async { [weak self] in self?.value = 1 } } }

Características clave:

  • El closure puede capturar valores por referencia o por valor
  • escaping/non-escaping afecta el ciclo de vida del closure
  • El closure como propiedades almacenadas fácilmente crea un retain cycle

Preguntas engañosas.

Si el closure no captura self explícitamente, ¿puede surgir un retain cycle?

Sí, si el closure se guarda como propiedad strong de la clase y accede a self dentro, se genera un retain cycle, incluso si no escribes self explícitamente. Usa [weak self]/[unowned self] o haz el closure local, si es posible.

¿Cuál es la diferencia entre un closure escaping y un non-escaping?

Un closure escaping puede ser llamado después de que la función haya terminado y se utiliza generalmente para operaciones asíncronas. El non-escaping se ejecuta antes de que la función termine. En el caso de escaping, el orden de captura de self es diferente.

Ejemplo de código:

func asyncWork(completion: @escaping () -> Void) { DispatchQueue.global().async { completion() } }

¿Puede un closure modificar los valores de las variables capturadas?

Sí, si el closure ha capturado una variable declarada como var, puede cambiar su valor (para tipos de valor). Para clases, será una referencia y siempre se pueden modificar las propiedades.

Ejemplo de código:

var value = 1 let closure = { value += 1 } closure() print(value) // 2

Errores comunes y anti-patrones

  • Configurar closures como propiedades de clase y acceder a self sin [weak self].
  • Declarar closures demasiado grandes — dificulta la comprensión y depuración.
  • Usar escaping donde se puede evitar con non-escaping.

Ejemplo de la vida real

Caso negativo

ViewController guarda un closure como propiedad (por ejemplo, completionHandler) y accede a self directamente dentro. Como resultado, se genera un retain cycle: ViewController => closure => ViewController. Desconectar la pantalla no libera memoria.

Ventajas: El código es conciso y corto a la vista.

Desventajas: Fugas de memoria, ViewController "se queda" en memoria, posibles errores.

Caso positivo

Se usa [weak self] o [unowned self] dentro del closure, o el closure se guarda por menos tiempo que el ciclo de vida del objeto. Revisar estos lugares en las revisiones de código.

Ventajas: Liberación correcta de recursos, ausencia de fugas impredecibles.

Desventajas: [weak self] requiere atención al desreferenciar, pueden ocurrir crashes implícitos si se usa incorrectamente.