Frage @noescape-Attribut in Swift 1.2


Es gibt ein neues Attribut in Swift 1.2 mit Verschlussparametern in Funktionen und wie in der Dokumentation steht:

Dies zeigt an, dass die   Parameter wird immer nur aufgerufen (oder als   @   Noescape-Parameter in einem Aufruf), was bedeutet, dass es nicht möglich ist   Überlebe die Lebensdauer des Anrufs.

Nach meinem Verständnis könnten wir vorher davon Gebrauch machen [weak self] den Verschluß nicht stark auf z.B. seine Klasse und self könnte null sein oder die Instanz, wenn die Schließung ausgeführt wird, aber jetzt, @noescape bedeutet, dass die Schließung niemals ausgeführt wird, wenn die Klasse entinitalisiert wird. Verstehe ich es richtig?

Und wenn ich richtig liege, warum sollte ich ein @noescape Schließung einer regulären Funktion, wenn sie sich sehr ähnlich verhält?


75
2018-02-10 08:51


Ursprung


Antworten:


@noescape kann so 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 () -> ()) {}

Hinzufügen @noescape garantiert, dass die Schließung nicht irgendwo gespeichert, zu einem späteren Zeitpunkt verwendet oder asynchron verwendet wird.

Aus Sicht des Aufrufers muss man sich nicht um die Lebensdauer der erfassten Variablen kümmern, da diese innerhalb der aufgerufenen Funktion oder gar nicht verwendet werden. Und als Bonus können wir eine implizite verwenden self, rettet uns vor dem Tippen 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 Versionshinweise):

Dies ermöglicht einige kleinere Leistungsoptimierungen.


143
2018-02-10 09:47



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

Es sind auch Optimierungen möglich, da nach der Zuweisung einer Variablen, die dann in einen Block eingepackt wird, sie nicht einfach am Ende der Funktion deallokiert werden kann. Also muss es auf dem Heap zugewiesen werden und ARC zum Dekonstruieren verwenden. In Objective-C müssen Sie das Schlüsselwort "__block" verwenden, um sicherzustellen, dass die Variable blockweise erstellt wird. Swift erkennt das automatisch, so dass das Keyword nicht benötigt wird, aber die Kosten sind gleich.

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 nicht einmal auf Null bezogene schwache Variablen sein (die teurer sind als unsichere Zeiger), da sie garantiert für die gesamte Lebensdauer des Blocks "lebendig" sind.

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


28
2018-02-10 20:33



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

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

Dies ist besonders nützlich beim Übergeben von nicht skalaren Werttypen (wie Enums, Strukturen, Closures), da das Kopieren von ihnen möglicherweise viel teurer ist als das einfache Übergeben eines Zeigers an den Stapel. Die Zuweisung der Instanz ist ebenfalls deutlich kostengünstiger (eine Anweisung im Gegensatz zu dem Aufruf von malloc ()). Es ist also ein Doppelgewinn, wenn der Compiler diese Optimierung vornehmen kann.

Ob Swift tatsächlich eine bestimmte Version des Swift-Compilers enthält oder nicht, müsste vom Swift-Team angegeben werden, oder Sie müssten den Quellcode lesen, wenn Sie ihn öffnen. Aus dem obigen Zitat über "kleine Optimierung" klingt es, als ob es entweder nicht, oder das Swift-Team hält es für "minderwertig". Ich würde es als eine wesentliche Optimierung betrachten.

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


8
2017-10-24 20:31