Pregunta atributo @noescape en Swift 1.2


Hay un nuevo atributo en Swift 1.2 con parámetros de cierre en funciones, y como dice la documentación:

Esto indica que el   el parámetro solo se llama (o pasa como   @   parámetro noescape en una llamada), lo que significa que no puede   sobrevivir la vida de la llamada.

En mi entendimiento, antes de eso, podríamos usar [weak self] no permitir que el cierre tenga una referencia fuerte a, p. su clase, y self puede ser nil o la instancia cuando se ejecuta el cierre, pero ahora, @noescape significa que el cierre nunca se ejecutará si la clase está desincronizada. ¿Lo entiendo correctamente?

Y si estoy en lo correcto, ¿por qué debería usar un @noescape cierre inssted de una función regular, cuando se comportan de manera muy similar?


75
2018-02-10 08:51


origen


Respuestas:


@noescape se puede usar así:

func doIt(code: @noescape () -> ()) {
    /* what we CAN */

    // just call it
    code()
    // pass it to another function as another `@noescape` parameter
    doItMore(code)
    // capture it in another `@noescape` closure
    doItMore {
        code()
    }

    /* what we CANNOT do *****

    // pass it as a non-`@noescape` parameter
    dispatch_async(dispatch_get_main_queue(), code)
    // store it
    let _code:() -> () = code
    // capture it in another non-`@noescape` closure
    let __code = { code() }

    */
}

func doItMore(code: @noescape () -> ()) {}

Añadiendo @noescape garantiza que el cierre no se almacenará en algún lugar, se utilizará en otro momento o se usará de forma asíncrona.

Desde el punto de vista de la persona que llama, no hay necesidad de preocuparse por la duración de las variables capturadas, ya que se utilizan dentro de la función llamada o no se utilizan en absoluto. Y como extra, podemos usar un implícito self, evitándonos escribir self..

func doIt(code: @noescape () -> ()) {
    code()
}

class Bar {
    var i = 0
    func some() {
        doIt {
            println(i)
            //      ^ we don't need `self.` anymore!
        }
    }
}

let bar = Bar()
bar.some() // -> outputs 0

Además, desde el punto de vista del compilador (como se documenta en Notas de lanzamiento)

Esto permite algunas optimizaciones de rendimiento menores.


143
2018-02-10 09:47



Una forma de pensarlo es que CADA variable dentro del bloque @noescape no necesita ser fuerte (no solo el yo).

También hay optimizaciones posibles ya que una vez que se asigna una variable que luego se envuelve en un bloque, no se puede desasignar normalmente al final de la función. Por lo tanto, debe asignarse en el Heap y usar ARC para deconstruir. En Objective-C, debe usar la palabra clave "__block" para asegurarse de que la variable se crea de forma amigable. Swift detectará automáticamente eso para que la palabra clave no sea necesaria, pero el costo es el mismo.

Si las variables se pasan a un bloque @nosecape, entonces pueden ser variables de pila, y no es necesario que ARC desasigne.

Las variables ahora ni siquiera necesitan ser variables débiles de referencia cero (que son más caras que los punteros inseguros) ya que se garantiza que estarán "vivas" durante la vida del bloque.

Todo esto da como resultado un código más rápido y más óptimo. Y reduce la sobrecarga para usar bloques de @autoclosure (que son muy útiles).


28
2018-02-10 20:33



(En referencia a la respuesta de Michael Gray anterior).

No estoy seguro de si esto está específicamente documentado para Swift, o si incluso el compilador de Swift lo aprovecha al máximo. Pero es diseño de compilador estándar asignar almacenamiento para una instancia en la pila si el compilador sabe que la función que se está llamando no intentará almacenar un puntero a esa instancia en el montón, y emitirá un error en tiempo de compilación si la función intenta hacerlo .

Esto es particularmente beneficioso cuando se pasan tipos de valores no escalares (como enums, structs, closures) porque copiarlos es potencialmente mucho más costoso que simplemente pasar un puntero a la pila. La asignación de la instancia también es significativamente menos costosa (una instrucción contra una llamada malloc ()). Entonces, es una doble ganancia si el compilador puede hacer esta optimización.

De nuevo, si el equipo de Swift debe declarar o no una versión determinada del compilador de Swift, o tendría que leer el código fuente cuando lo abren. De la cita anterior sobre "optimización menor", parece que no es así, o el equipo Swift la considera "menor". Lo consideraría una optimización significativa.

Presumiblemente, el atributo está ahí para que (al menos en el futuro) el compilador pueda realizar esta optimización.


8
2017-10-24 20:31