Me parece lógico que escapar de los cierres capture las estructuras mediante la copia. Pero si ese fuera el caso, el siguiente código no tiene sentido y no debería compilarse:
struct Wtf { var x = 1 } func foo(){ var wtf = Wtf() DispatchQueue.global().async { wtf.x = 5 } Thread.sleep(forTimeInterval: 2) print("x = \(wtf.x)") }Sin embargo, se compila con éxito e incluso imprime 5 cuando se llama a foo. ¿Cómo es esto posible?
Si bien puede tener sentido que se copie una estructura, como lo demuestra su código, no lo es. Esa es una herramienta poderosa. Por ejemplo:
func makeCounter() -> () -> Int { var n = 0 return { n += 1 // This `n` is the same `n` from the outer scope return n } // At this point, the scope is gone, but the `n` lives on in the closure. } let counter1 = makeCounter() let counter2 = makeCounter() print("Counter1: ", counter1(), counter1()) // Counter1: 1 2 print("Counter2: ", counter2(), counter2()) // Counter2: 1 2 print("Counter1: ", counter1(), counter1()) // Counter1: 3 4 Si se copiara n en el cierre, esto no podría funcionar. El punto es que el cierre captura y puede modificar el estado fuera de sí mismo. Esto es lo que separa un cierre (que "cierra" el ámbito donde se creó) y una función anónima (que no lo hace).
(La historia del término "cerrar" es un poco oscura. Se refiere a la idea de que las variables libres de la expresión lambda han sido "cerradas", pero en mi opinión, "atado" sería un término mucho más obvio, y así es como describimos esto en todas partes. Pero el término "cierre" se ha utilizado durante décadas, así que aquí estamos.)
Tenga en cuenta que es posible obtener semántica de copia. Solo tienes que pedirlo:
func foo(){ var wtf = Wtf() DispatchQueue.global().async { [wtf] in // Make a local `let` copy var wtf = wtf // To modify it, we need to make a `var` copy wtf.x = 5 } Thread.sleep(forTimeInterval: 2) // Prints 1 as you expected print("x = \(wtf.x)") }En C++, las lambdas tienen que ser explícitas acerca de cómo capturar valores, vinculando o copiando. Pero en Swift, optaron por hacer que el enlace sea el predeterminado.
En cuanto a por qué se le permite acceder a wtf después de que haya sido capturado por el cierre, eso es solo una falta de semántica de movimiento en Swift. No hay forma en Swift hoy de expresar "esta variable se ha pasado a otra cosa y ya no se puede acceder a ella en este ámbito". Esa es una limitación conocida del lenguaje, y se está trabajando mucho para solucionarlo. Consulte el Manifiesto de propiedad para obtener más información.