@ noescape-Attribut in Swift 1.2

75

In Swift 1.2 gibt es ein neues Attribut mit Schließparametern in Funktionen, und wie in der Dokumentation angegeben:

Dies zeigt an, dass der Parameter immer nur aufgerufen wird (oder in einem Aufruf als @ noescape-Parameter übergeben wird), was bedeutet, dass er die Lebensdauer des Aufrufs nicht überleben kann.

Nach meinem Verständnis könnten wir vorher verwenden, [weak self]um nicht zuzulassen, dass der Abschluss einen starken Bezug zu z. B. seiner Klasse hat, und self könnte null oder der Fall sein, in dem der Abschluss ausgeführt wird, aber jetzt @noescapebedeutet dies, dass der Abschluss niemals ausgeführt wird wenn die Klasse deinitalisiert ist. Verstehe ich es richtig

Und wenn ich richtig @noescapeliege , warum sollte ich dann einen Verschluss verwenden, der eine reguläre Funktion enthält, wenn sie sich sehr ähnlich verhalten?

Dániel Nagy
quelle

Antworten:

143

@noescape kann wie folgt verwendet werden:

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

Durch @noescapedas Hinzufügen wird garantiert, dass der Verschluss nicht irgendwo gespeichert, zu einem späteren Zeitpunkt verwendet oder asynchron verwendet wird.

Aus Sicht des Aufrufers besteht keine Notwendigkeit, sich um die Lebensdauer der erfassten Variablen zu kümmern, da diese innerhalb der aufgerufenen Funktion verwendet werden oder überhaupt nicht. Und als Bonus können wir ein implizites verwenden self, das uns vor dem Tippen bewahrt 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

Auch aus Sicht des Compilers (wie in den Versionshinweisen dokumentiert ):

Dies ermöglicht einige kleinere Leistungsoptimierungen.

Rintaro
quelle
5
Das Schlüsselstück für mich: " @noescapegarantiert, dass der Verschluss nicht ... asynchron verwendet wird". Das heißt, Sie können es nicht mit Netzwerkcode verwenden, der asynchron ausgelöst wird.
David James
28

Eine Möglichkeit, darüber nachzudenken, besteht darin, dass JEDE Variable innerhalb des @ noescape-Blocks nicht stark sein muss (nicht nur das Selbst).

Es sind auch Optimierungen möglich, da eine einmal zugewiesene Variable, die dann in einen Block eingeschlossen wird, nicht einfach am Ende der Funktion freigegeben werden kann. Es muss also auf dem Heap zugewiesen werden und mit ARC dekonstruiert werden. In Objective-C müssen Sie das Schlüsselwort "__block" verwenden, um sicherzustellen, dass die Variable blockfreundlich erstellt wird. Swift erkennt dies automatisch, sodass das Schlüsselwort nicht benötigt wird, die Kosten jedoch gleich sind.

Wenn die Variablen an einen @ nosecape-Block übergeben werden, können sie Stapelvariablen sein und benötigen keine ARC, um die Zuordnung aufzuheben.

Die Variablen müssen jetzt nicht einmal schwache Variablen sein, die auf Null verweisen (die teurer sind als unsichere Zeiger), da sie während der gesamten Lebensdauer des Blocks garantiert "lebendig" sind.

All dies führt zu einem schnelleren und optimaleren Code. Und reduziert den Aufwand für die Verwendung von @ autoclosure-Blöcken (die sehr nützlich sind).

Michael Gray
quelle
Können Sie eine Referenz geben, wo ich über das lesen kann, was Sie hier angegeben haben? Diese Details würden mich wirklich interessieren.
Dániel Nagy
Siehe meine Antwort unten.
David Goodine
8

(In Bezug auf die Antwort von Michael Gray oben.)

Ich bin mir nicht sicher, ob dies speziell für Swift dokumentiert ist oder ob sogar der Swift-Compiler es voll ausnutzt. Es ist jedoch Standard-Compiler-Design, Speicher für eine Instanz auf dem Stapel zuzuweisen, wenn der Compiler weiß, dass die aufgerufene Funktion nicht versucht, einen Zeiger auf diese Instanz im Heap zu speichern, und einen Kompilierungsfehler ausgibt, wenn die Funktion dies versucht .

Dies ist besonders nützlich, wenn nicht skalare Werttypen (wie Aufzählungen, Strukturen, Abschlüsse) übergeben werden, da das Kopieren möglicherweise viel teurer ist als das einfache Übergeben eines Zeigers an den Stapel. Das Zuweisen der Instanz ist auch erheblich kostengünstiger (eine Anweisung im Vergleich zum Aufrufen von malloc ()). Es ist also ein doppelter Gewinn, wenn der Compiler diese Optimierung vornehmen kann.

Auch hier muss das Swift-Team angeben, ob eine bestimmte Version des Swift-Compilers tatsächlich funktioniert oder nicht, oder Sie müssen den Quellcode lesen, wenn sie ihn als Open-Source-Version bereitstellen. Aus dem obigen Zitat über "geringfügige Optimierung" geht hervor, dass dies entweder nicht der Fall ist oder dass das Swift-Team dies als "geringfügig" ansieht. Ich würde es als signifikante Optimierung betrachten.

Vermutlich ist das Attribut vorhanden, damit der Compiler (zumindest in Zukunft) diese Optimierung durchführen kann.

David Goodine
quelle