Función de desmitificación @escape, @no escape, @autoclosure y curry
Los cierres son bloques de funcionalidad autónomos que se pueden transmitir y utilizar en el código.
— Apple
Los cierres pueden capturar y almacenar referencias a cualquier constante y variable del contexto en el que se definen, lo que se conoce como cierre, por lo tanto, Cierre. Puede pensar en un cierre como una función que no tiene un nombre propio y captura los valores de su entorno. Las funciones y los cierres son objetos de primera clase en Swift: puede almacenarlos, pasarlos como argumentos a funciones y tratarlos como lo haría con cualquier otro valor u objeto. Pasar cierres como controladores de finalización es un patrón común en muchas API. La biblioteca Swift estándar utiliza cierres principalmente para el manejo de eventos y devoluciones de llamada.
Las funciones son fragmentos de código autónomos que realizan una tarea específica. A una función se le da un nombre que identifica lo que hace, y este nombre se usa para «llamar» a la función para que realice su tarea cuando sea necesario. Se define una función con la palabra clave func
. Funciones puede tomar ninguno a muchos parámetros, variadic parámetros y devolver ninguno o varios parámetros.
Tipos de función
El tipo de función se compone de los tipos de parámetros y el tipo de retorno de la función. Para el ejemplo anterior, el tipo de función es:(Int, Int) -> Int
Esto se puede leer como: «Una función que tiene dos parámetros, ambos de tipo Int
y que devuelve un valor de tipo Int
.»El tipo de función se puede establecer como parámetro o tipo de función de retorno.
Los tipos de función se pueden asignar a cualquier variable como esta:
var mathFunction: (Int, Int) -> Int = add
Las funciones son casos especiales de cierres. Los cierres adoptan una de tres formas:
- Funciones globales: Tienen un nombre y no pueden capturar el valor.
- Funciones anidadas: Tienen un nombre y pueden capturar valores de la función que las encierra.
- Expresiones de cierre: No tienen nombre y pueden capturar valores de su contexto circundante.
Expresión de cierre:
El cierre se puede crear colocando un tipo de función dentro de llaves y una palabra clave in
después del tipo de retorno.
Nombres de argumento abreviados
Los argumentos de cierre pueden referirse a una posición, p. ej. ,
,
,
y así sucesivamente.
Retornos implícitos de Closure:
Los cierres de expresión única pueden devolver implícitamente el resultado de su expresión única omitiendo la palabra clave return
de su declaración.
Para un cierre de expresión multilínea, no se puede omitir la palabra clave return
.
Cierre de arrastre:
Si necesita pasar una expresión de cierre a una función como último argumento de la función y la expresión de cierre es demasiado larga, se puede escribir como cierre final. Un cierre final se escribe después de los paréntesis () de la llamada a la función, aunque sigue siendo un argumento de la función. Cuando se utiliza la sintaxis de cierre final, no se escribe la etiqueta de argumento para el cierre como parte de la llamada a la función.
Si el cierre es el último parámetro de un método, swift le permite escribir de esta manera 🖕
El uso de la sintaxis de cierre final encapsula cuidadosamente la funcionalidad del cierre inmediatamente después de la función que admite el cierre, sin necesidad de envolver todo el cierre dentro de los paréntesis exteriores del método reduce(_:)
.
Captura de valores:
Un cierre puede capturar constantes y variables del contexto circundante en el que se define. El cierre puede hacer referencia y modificar los valores de esas constantes y variables desde dentro de su cuerpo, incluso si el ámbito original que definió las constantes y variables ya no existe.
En Swift, la forma más simple de un cierre que puede capturar valores es una función anidada, escrita dentro del cuerpo de otra función. Una función anidada puede capturar cualquiera de los argumentos de su función externa y también puede capturar cualquier constante y variable definida dentro de la función externa.
Esta función makeIncrementer
acepta un argumento, es decir, Int, como entrada y devuelve un tipo de función, es decir, () -> Int
. Esto significa que devuelve una función, en lugar de un valor simple. La función que devuelve no tiene parámetros, y devuelve un valor Int
cada vez que se llama.
Aquí amount
es el argumento, runningTotal
se declara como variable y se inicializa con 0. La función anidada incrementer
captura amount
y runningTotal
del contexto circundante.
Vamos a ver makeIncrementer
en acción:
Nota: Como optimización, Swift puede capturar y almacenar una copia de un valor si ese valor no está mutado por un cierre y si el valor no está mutado después de crear el cierre.
Swift también se encarga de toda la gestión de memoria que implica la eliminación de variables cuando ya no son necesarias.
Para deshacerse de la expresión de cierre largo en el argumento de función, puede usar typealias.
Cierres sin escape:
Los parámetros de cierre se escapaban de forma predeterminada antes de Swift 3. Un cierre no escaparía al cuerpo de la función si los parámetros de cierre están marcados como sin escape
En Swift 3 se ha invertido. Cuando pasa un cierre como argumento de función, el cierre se ejecuta con el cuerpo de la función y devuelve el compilador. A medida que la ejecución termina, el cierre pasado se sale del alcance y no tiene más existencia en la memoria.
Lo menos que necesita Saber
Los parámetros de cierre no se escapan de forma predeterminada, si desea escapar de la ejecución de cierre, debe usar @ escaping con los parámetros de cierre.
Ciclo de vida del cierre sin escape:
1. Pase el cierre como argumento de función, durante la llamada a la función.
2. Haga un poco de trabajo en función y luego ejecute el cierre.
3. Devuelve la función.
Debido a una mejor gestión y optimización de la memoria, Swift ha cambiado todos los cierres para que no se escapen de forma predeterminada. CaptureList.swift
es un ejemplo de cierre sin escape.
Nota: la anotación @sin escape se aplica solo a tipos de función
Cierres de escape:
Se dice que un cierre escapa de una función cuando el cierre se pasa como argumento a la función, pero se llama después de que la función regresa. Marcar un cierre con @escaping
significa que debe hacer referencia explícita a self
dentro del cierre.
Ciclo de vida del cierre @ escaping:
1. Pase el cierre como argumento de función, durante la llamada a la función.
2. Haga algún trabajo adicional en función.
3. Función ejecutar el cierre de forma asíncrona o almacenada.
4. Devuelve la función.
Veamos dónde se escapan los cierres por defecto:
- Las variables de tipo de función son de escape implícito
- Las variantes de tipo son de escape implícito
- Los cierres opcionales son de escape implícito
Error común:
Asignar un cierre sin escape al cierre de escape. Hay 2 maneras de arreglar esto:
- Marque el cierre como escape
- O mantenga el comportamiento predeterminado @noescape haciendo que el cierre sea opcional
Autoclosures:
El atributo @autoclosure
de Swift le permite definir un argumento que se envuelve automáticamente en un cierre. No toma ningún argumento, y cuando se llama, devuelve el valor de la expresión que está envuelta dentro de ella. Esta conveniencia sintáctica le permite omitir llaves alrededor del parámetro de una función escribiendo una expresión normal en lugar de un cierre explícito.
Por ejemplo, la función assert(condition:message:file:line:)
toma un autoclosure para sus parámetros condition
y message
; su parámetro condition
solo se evalúa en compilaciones de depuración y su parámetro message
solo se evalúa si condition
es false
.
func assert(_ expression: @autoclosure () -> Bool,
_ message: @autoclosure () -> String) {}
Para usar @autoclosure
con @escaping
la sintaxis de atributo es:
@autoclosure @escaping () -> Bool
Cierres vs Bloques:
» Los cierres rápidos y los bloques de Objective-C son compatibles para que pueda pasar cierres rápidos a métodos de Objective-C que esperan bloques. Los cierres y funciones Swift tienen el mismo tipo, por lo que incluso puede pasar el nombre de una función Swift. Los cierres tienen una semántica de captura similar a la de los bloques, pero difieren en una forma clave: las variables son mutables en lugar de copiadas. En otras palabras, el comportamiento de _ _ block en Objective-C es el comportamiento predeterminado para las variables en Swift.»
Cierres vs Delegados:
La solución depende del problema. Además, Apple está cambiando su enfoque al patrón de devolución de llamada. UIAlertAction
es un ejemplo de esto.
https://medium.com/@abhimuralidharan/functional-swift-all-about-closures-310bc8af31dd
https://medium.com/@kumarpramod017/what-do-mean-escaping-and-nonescaping-closures-in-swift-d404d721f39d
https://oleb.net/blog/2016/10/optional-non-escaping-closures/
https://swiftunboxed.com/lang/closures-escaping-noescape-swift3/
https://medium.com/@johnsundell/using-autoclosure-when-designing-swift-apis-67fe20a8b2e