examples - swift functions
Escape de cierres en Swift (5)
Swift 4.1
De la referencia del lenguaje: atributos del lenguaje de programación Swift (Swift 4.1)
Apple explica el atributo
escaping
claramente.
Aplique este atributo al tipo de un parámetro en una declaración de método o función para indicar que el valor del parámetro puede almacenarse para su posterior ejecución. Esto significa que el valor puede sobrevivir a la duración de la llamada. Los parámetros de tipo de función con el atributo de tipo de escape requieren el uso explícito de self. para propiedades o métodos. Para ver un ejemplo de cómo usar el atributo de escape, vea Escapar de cierres
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
La función
someFunctionWithEscapingClosure(_:)
toma un cierre como argumento y lo agrega a una matriz que se declara fuera de la función. Si no marcó el parámetro de esta función con@escaping
, obtendría un error en tiempo de compilación.Se dice que un cierre escapa de una función cuando el cierre se pasa como un argumento a la función, pero se llama después de que la función regrese. Cuando declara una función que toma un cierre como uno de sus parámetros, puede escribir @escaping antes del tipo del parámetro para indicar que el cierre puede escapar.
Soy nuevo en Swift y estaba leyendo el manual cuando me encontré con escapes cerrados. No recibí la descripción del manual en absoluto. ¿Podría alguien explicarme en qué consisten los cierres de escape en Swift en términos simples?
Considera esta clase:
class A {
var closure: (() -> Void)?
func someMethod(closure: () -> Void) {
self.closure = closure
}
}
someMethod
asigna el cierre pasado a una propiedad en la clase.
Ahora aquí viene otra clase:
class B {
var number = 0
var a: A = A()
func anotherMethod() {
a.someMethod { self.number = 10 }
}
}
Si llamo a otro
anotherMethod
, el cierre
{ self.number = 10 }
se almacenará en la instancia de
A
Como
self
se captura en el cierre, la instancia de
A
también tendrá una fuerte referencia a él.
¡Eso es básicamente un ejemplo de un cierre escapado!
Probablemente se esté preguntando, "¿qué? Entonces, ¿de dónde escapó el cierre y hacia dónde?"
El cierre escapa del alcance del método, al alcance de la clase. ¡Y se puede llamar más tarde, incluso en otro hilo! Esto podría causar problemas si no se maneja adecuadamente.
Para evitar escaparse accidentalmente de cierres y causar ciclos de retención y otros problemas, use el atributo
@noescape
:
class A {
var closure: (() -> Void)?
func someMethod(@noescape closure: () -> Void) {
}
}
Ahora, si intentas escribir
self.closure = closure
, ¡no se compila!
Actualizar:
En Swift 3, todos los parámetros de cierre no pueden escapar por defecto.
Debe agregar el atributo
@escaping
para que el cierre pueda escapar del alcance actual.
¡Esto agrega mucha más seguridad a su código!
class A {
var closure: (() -> Void)?
func someMethod(closure: @escaping () -> Void) {
}
}
Por defecto, los cierres no se escapan. Para una comprensión simple, puede considerar los cierres que no escapan como cierre local (al igual que las variables locales) y el escape como cierre global (al igual que las variables globales). Significa que una vez que salimos del cuerpo del método, se pierde el alcance del cierre sin escape. Pero en el caso de cierre de escape, la memoria retuvo el cierre en la memoria.
*** Simplemente usamos el cierre de escape cuando llamamos al cierre dentro de cualquier tarea asíncrona en el método, o el método regresa antes de llamar al cierre.
Cierre sin escape: -
func add(num1: Int, num2: Int, completion: ((Int) -> (Void))) -> Int {
DispatchQueue.global(qos: .background).async {
print("Background")
completion(num1 + num2) // Error: Closure use of non-escaping parameter ''completion'' may allow it to escape
}
return num1
}
override func viewDidLoad() {
super.viewDidLoad()
let ans = add(num1: 12, num2: 22, completion: { (number) in
print("Inside Closure")
print(number)
})
print("Ans = /(ans)")
initialSetup()
}
Como se trata de un cierre sin escape, su alcance se perderá una vez que salgamos del método ''agregar''. la finalización (num1 + num2) nunca llamará.
Cierre de escape: -
func add(num1: Int, num2: Int, completion: @escaping((Int) -> (Void))) -> Int {
DispatchQueue.global(qos: .background).async {
print("Background")
completion(num1 + num2)
}
return num1
}
Incluso si el método devuelve (es decir, salimos del alcance del método) se llamará al cierre.
enter code here
Voy de una manera más simple.
Considere este ejemplo:
func testFunctionWithNonescapingClosure(closure:() -> Void) {
closure()
}
Lo anterior es un cierre sin escape porque el cierre se invoca antes de que regrese el método.
Considere el mismo ejemplo con una operación asíncrona:
func testFunctionWithEscapingClosure(closure:@escaping () -> Void) {
DispatchQueue.main.async {
closure()
}
}
El ejemplo anterior contiene un cierre de escape porque la invocación de cierre puede ocurrir después de que la función regrese debido a la operación asincrónica.
var completionHandlers: [() -> Void] = []
func testFunctionWithEscapingClosure(closure: @escaping () -> Void) {
completionHandlers.append(closure)
}
En el caso anterior, puede darse cuenta fácilmente de que el cierre se mueve fuera del cuerpo de la función, por lo que debe ser un cierre de escape.
Se agregaron cierres con y sin escape para la optimización del compilador en Swift 3. Puede buscar las ventajas del cierre sin
nonescaping
.
Creo que este sitio web es muy útil al respecto. Una explicación simple sería:
Si se pasa un cierre como argumento a una función y se invoca después de que la función regrese, el cierre se escapa.
¡Lea más en el enlace que pasé arriba! :)