ProgramaciónDesarrollador iOS

Explique el funcionamiento de los closures en Swift: diferencias con funciones, características de captura de variables, posibles problemas con un uso incorrecto.

Supere entrevistas con el asistente de IA Hintsage

Respuesta

Los closures en Swift son bloques de código independientes que pueden capturar y mantener referencias a variables y constantes del contexto circundante. Permiten organizar callbacks, procesamiento asíncrono y almacenar la ejecución del código como valores de variables.

Diferencias con las funciones

  • Los closures pueden capturar variables del ámbito externo.
  • Tienen una sintaxis más compacta.
  • Pueden ser pasados como valores.

Ejemplo de un closure:

var contador = 0 let incrementador: () -> Void = { contador += 1 } incrementador() print(contador) // 1

Características de captura

  • Por defecto, las variables se capturan strong (por referencia fuerte).
  • Se pueden definir explícitamente las reglas de captura a través de la lista de captura ([weak self], [unowned self]).

Problemas

  • El problema principal es el posible retain cycle al capturar self dentro del closure.
  • En código multihilo, se pueden capturar inadvertidamente valores obsoletos de las variables.

Pregunta trampa

¿Cuál es la diferencia entre strong-capture y el uso de [weak self] o [unowned self] en la lista de captura de un closure? ¿Qué implica esto?

Respuesta: Si no especificas [weak self] o [unowned self], el closure capturará self por referencia fuerte por defecto, lo que llevará a un retain cycle si el closure y self se refieren entre sí. [weak self] captura self por referencia débil (opcional), [unowned self] — por referencia no controlada (no opcional). Una elección incorrecta o la ausencia de una lista de captura conduce a fugas de memoria o fallos.

class Test { var closure: (() -> Void)? func setup() { closure = { [weak self] in self?.doWork() } } func doWork() { ... } }

Ejemplos de errores reales debido al desconocimiento de los matices del tema


Historia

Un programador no utilizó la lista de captura en un closure dentro de UIViewController, que iniciaba una carga de datos asíncrona. Como resultado, el controller y su vista no se liberaron, lo que llevó a una fuga de memoria después de cerrar (dismiss). El análisis de la infraestructura mostró cientos de controllers "atrapados" en la memoria.


Historia

El closure capturó self con [unowned self], pero el ciclo de vida de self terminó antes que el closure. Esto causó un fallo al intentar acceder a un objeto ya liberado — la aplicación se cerró al intentar trabajar con self dentro del closure.


Historia

En el proyecto, dentro de un closure anidado, se capturaron dos variables: una por referencia fuerte, la otra por débil. Resultó que la referencia débil se anulaba demasiado pronto, y los datos necesarios para el funcionamiento del closure se perdieron — el error se manifestó solo en una prueba de estrés bajo carga en un entorno multihilo.