web-dev-qa-db-fra.com

Attribut @noescape dans Swift 1.2

Il y a un nouvel attribut dans Swift 1.2 avec des paramètres de fermeture dans les fonctions, et comme le dit la documentation:

Cela indique que le paramètre est uniquement appelé (ou passé en tant que paramètre @ noescape dans un appel), ce qui signifie qu'il ne peut pas survivre à la durée de vie de l'appel.

À ma connaissance, avant cela, nous pourrions utiliser [weak self] pour ne pas laisser la fermeture avoir une référence forte, par exemple sa classe et self peuvent être nuls ou l'instance lorsque la fermeture est exécutée, mais maintenant, @noescape signifie que la fermeture ne sera jamais exécutée si la classe est désinitalisée. Suis-je bien compris?

Et si j'ai raison, pourquoi devrais-je utiliser un @noescape fermeture insted d'une fonction régulière, quand ils se comportent très similaires?

75
Dániel Nagy

@noescape peut être utilisé comme ceci:

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 () -> ()) {}

Ajouter @noescape garantit que la fermeture ne sera pas stockée quelque part, utilisée ultérieurement ou utilisée de manière asynchrone.

Du point de vue de l'appelant, il n'est pas nécessaire de se soucier de la durée de vie des variables capturées, car elles sont utilisées ou pas du tout dans la fonction appelée. Et en bonus, nous pouvons utiliser un self implicite, ce qui nous évite de taper 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

En outre, du point de vue du compilateur (comme documenté dans notes de version ):

Cela permet quelques optimisations de performances mineures.

143
rintaro

Une façon de penser à cela, c'est que CHAQUE variable à l'intérieur du bloc @noescape n'a pas besoin d'être Strong (pas seulement le soi).

Il y a aussi des optimisations possibles car une fois qu'une variable est allouée puis encapsulée dans un bloc, elle ne peut pas simplement être normalement désallouée à la fin de la fonction. Il doit donc être alloué sur le tas et utiliser ARC pour déconstruire. Dans Objective-C, vous devez utiliser le mot clé "__block" pour vous assurer que la variable est créée de manière conviviale pour les blocs. Swift détectera automatiquement cela afin que le mot-clé ne soit pas nécessaire, mais le coût est le même.

Si les variables sont transmises à un bloc @nosecape, elles peuvent être des variables de pile et n'ont pas besoin d'ARC pour se désallouer.

Il n'est désormais plus nécessaire que les variables soient des variables faibles de référence zéro (qui sont plus chères que les pointeurs dangereux) car elles seront garanties "vivantes" pendant toute la durée de vie du bloc.

Tout cela se traduit par un code plus rapide et plus optimal. Et réduit les frais généraux liés à l'utilisation des blocs @autoclosure (qui sont très utiles).

28
Michael Gray

(En référence à la réponse de Michael Gray ci-dessus.)

Je ne sais pas si cela est spécifiquement documenté pour Swift, ou si même le compilateur Swift en tire pleinement parti. Mais c'est la conception standard du compilateur pour allouer le stockage d'une instance sur la pile si le compilateur connaît le La fonction appelée n'essayera pas de stocker un pointeur vers cette instance dans le tas et émettra une erreur de compilation si la fonction tente de le faire.

Cela est particulièrement avantageux lors du passage de types de valeurs non scalaires (comme des énumérations, des structures, des fermetures) car les copier est potentiellement beaucoup plus coûteux que de simplement passer un pointeur vers la pile. L'allocation de l'instance est également beaucoup moins coûteuse (une instruction par rapport à l'appel à malloc ()). C'est donc un double gain si le compilateur peut faire cette optimisation.

Encore une fois, si oui ou non une version donnée du compilateur Swift doit être indiquée par l'équipe Swift, ou vous devrez lire le le code source quand ils l'open source. D'après la citation ci-dessus à propos de "l'optimisation mineure", il semble que ce ne soit pas le cas, ou que l'équipe Swift le considère comme "mineur". Je considérerais c'est une optimisation importante.

On peut supposer que l'attribut est là pour que (au moins à l'avenir) le compilateur puisse effectuer cette optimisation.

8
David Goodine