Manejador de finalización y escape rápido

100

Estoy tratando de entender el "cierre" de Swift con mayor precisión.

Pero @escapingy Completion Handlerson demasiado difíciles de entender

Busqué muchas publicaciones de Swift y documentos oficiales, pero sentí que todavía no era suficiente.

Este es el ejemplo de código de documentos oficiales.

var completionHandlers: [()->Void] = []

func someFunctionWithEscapingClosure(completionHandler: @escaping ()->Void){
    completionHandlers.append(completionHandler)
}

func someFunctionWithNoneescapingClosure(closure: ()->Void){
    closure()
}

class SomeClass{
    var x:Int = 10
    func doSomething(){
        someFunctionWithEscapingClosure {
            self.x = 100
            //not excute yet
        }
        someFunctionWithNoneescapingClosure {
            x = 200
        }
    }
}

let instance = SomeClass()
instance.doSomething()
print(instance.x)

completionHandlers.first?() 
print(instance.x)

Escuché que hay dos formas y razones para usar @escaping

El primero es para almacenar un cierre, el segundo es para fines operativos Async.

Las siguientes son mis preguntas :

Primero, si se doSomethingejecuta, someFunctionWithEscapingClosurese ejecutará con el parámetro de cierre y ese cierre se guardará en la matriz de variables globales.

Creo que el cierre es {self.x = 100}

¿Cómo selfen {self.x = 100} esa variable global guardada completionHandlerspuede conectarse a instanceese objeto de SomeClass?

En segundo lugar, entiendo someFunctionWithEscapingClosureasí.

¡Para almacenar el cierre de la variable local completionHandleren la we usingpalabra clave global 'completeHandlers @ escaping`!

sin devoluciones de @escapingpalabras clave someFunctionWithEscapingClosure, la variable local completionHandlerse eliminará de la memoria

@escaping es mantener ese cierre en la memoria

¿Es esto correcto?

Por último, me pregunto acerca de la existencia de esta gramática.

Quizás esta sea una pregunta muy rudimentaria.

Si queremos que alguna función se ejecute después de alguna función específica. ¿Por qué no llamamos a alguna función después de una llamada a una función específica?

¿Cuáles son las diferencias entre usar el patrón anterior y usar una función de devolución de llamada de escape?

Dongkun Lee
fuente

Respuestas:

123

Manejador de finalización rápida que escapa y no escapa:

Como explica Bob Lee en la publicación de su blog Completion Handlers en Swift con Bob :

Suponga que el usuario está actualizando una aplicación mientras la usa. Definitivamente desea notificar al usuario cuando haya terminado. Es posible que desee que aparezca un cuadro que diga: "¡Felicitaciones, ahora puede disfrutar plenamente!"

Entonces, ¿cómo se ejecuta un bloque de código solo después de que se haya completado la descarga? Además, ¿cómo se animan ciertos objetos solo después de que un controlador de vista se haya movido al siguiente? Bueno, vamos a descubrir cómo diseñar uno como un jefe.

Según mi extensa lista de vocabulario, los controladores de finalización representan

Hacer cosas cuando se hayan hecho las cosas

La publicación de Bob proporciona claridad sobre los controladores de finalización (desde el punto de vista del desarrollador, define exactamente lo que debemos comprender).

@ cierres de escape:

Cuando uno pasa un cierre en los argumentos de la función, usándolo después de que se ejecuta el cuerpo de la función y devuelve el compilador. Cuando finaliza la función, el alcance del cierre pasado existe y tiene existencia en la memoria, hasta que se ejecuta el cierre.

Hay varias formas de escapar del cierre en la función contenedora:

  • Almacenamiento: cuando necesite almacenar el cierre en la variable global, propiedad o cualquier otro almacenamiento que exista en el pasado de la memoria de la función de llamada, ejecute y devuelva el compilador.

  • Ejecución asincrónica: cuando está ejecutando el cierre de forma asincrónica en la cola de envío, la cola mantendrá el cierre en la memoria para usted, que se puede utilizar en el futuro. En este caso, no tiene idea de cuándo se ejecutará el cierre.

Cuando intente utilizar el cierre en estos escenarios, el compilador Swift mostrará el error:

captura de pantalla de error

Para obtener más claridad sobre este tema, puede consultar esta publicación en Medium .

Añadiendo un punto más, que todo desarrollador de iOS debe comprender:

  1. Cierre de escape : un cierre de escape es un cierre que se llama después de que la función a la que se pasó a devuelve. En otras palabras, sobrevive a la función a la que se le pasó.
  2. Cierre sin escape : un cierre que se llama dentro de la función a la que se pasó, es decir, antes de que regrese.
Shobhakar Tiwari
fuente
@shabhakar, ¿qué pasa si almacenamos un cierre pero no lo llamamos más tarde? O si el método fue llamado dos veces pero llamamos al cierre solo una vez. Ya que sabemos que el resultado es el mismo.
user1101733
@ user1101733 Creo que estás hablando de escapar del cierre, el cierre no se ejecutará hasta que no llames. En el ejemplo anterior, si se llama al método doSomething 2 veces 2, el objeto CompletionHandler se agregará en la matriz CompletionHandlers. Si toma el primer objeto de la matriz completeHandlers y lo llama, se ejecutará, pero el recuento de la matriz completeHandlers seguirá siendo el mismo (2).
Deepak
@Deepak, Sí sobre el cierre de escape. Supongamos que no usamos una matriz y usamos una variable normal para almacenar la referencia de cierre, ya que tenemos que ejecutar la llamada más reciente. ¿Alguna memoria ocupada por cierres anteriores que nunca llamarán?
user1101733
1
@ user1101733 Los cierres son de tipo de referencia (como clase), cuando asigna nuevos cierres a la variable, la propiedad / variable apuntará a un nuevo cierre para que ARC desasigne la memoria para cierres anteriores.
Deepak
28

Aquí hay una pequeña clase de ejemplos que utilizo para recordarme cómo funciona @escaping.

class EscapingExamples: NSObject {

    var closure: (() -> Void)?

    func storageExample(with completion: (() -> Void)) {
        //This will produce a compile-time error because `closure` is outside the scope of this
        //function - it's a class-instance level variable - and so it could be called by any other method at
        //any time, even after this function has completed. We need to tell `completion` that it may remain in memory, i.e. `escape` the scope of this
        //function.
        closure = completion
        //Run some function that may call `closure` at some point, but not necessary for the error to show up.
        //runOperation()
    }

    func asyncExample(with completion: (() -> Void)) {
        //This will produce a compile-time error because the completion closure may be called at any time
        //due to the async nature of the call which precedes/encloses it.  We need to tell `completion` that it should
        //stay in memory, i.e.`escape` the scope of this function.
        DispatchQueue.global().async {
            completion()
        }
    }

    func asyncExample2(with completion: (() -> Void)) {
        //The same as the above method - the compiler sees the `@escaping` nature of the
        //closure required by `runAsyncTask()` and tells us we need to allow our own completion
        //closure to be @escaping too. `runAsyncTask`'s completion block will be retained in memory until
        //it is executed, so our completion closure must explicitly do the same.
        runAsyncTask {
            completion()
        }
    }





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

}
JamesK
fuente
2
Este código no es correcto. Faltan las @escapingeliminatorias.
Rob
Me gustó más estoi.e. escape the scope of this function.
Gal