Entmystifizierung von @escaping, @non-escaping, @autoclosure und Curry-Funktion
Closures sind in sich geschlossene Funktionsblöcke, die weitergegeben und in Ihrem Code verwendet werden können.
— Apple
Closures können Verweise auf beliebige Konstanten und Variablen aus dem Kontext, in dem sie definiert sind, erfassen und speichern. Sie können sich einen Abschluss als eine Funktion vorstellen, die keinen eigenen Namen hat und Werte aus ihrer Umgebung erfasst. Funktionen und Closures sind erstklassige Objekte in Swift: Sie können sie speichern, als Argumente an Funktionen übergeben und wie jeden anderen Wert oder jedes andere Objekt behandeln. Das Übergeben von Closures als Completion-Handler ist ein häufiges Muster in vielen APIs. Die Standard-Swift-Bibliothek verwendet Closures hauptsächlich für die Ereignisbehandlung und Rückrufe.
Funktionen sind in sich geschlossene Codeblöcke, die eine bestimmte Aufgabe ausführen. Sie geben einer Funktion einen Namen, der angibt, was sie tut, und dieser Name wird verwendet, um die Funktion bei Bedarf „aufzurufen“, um ihre Aufgabe auszuführen. Sie definieren eine Funktion mit dem Schlüsselwort func
. Funktionen können keine bis viele Parameter, variadische Parameter und keine oder mehrere Parameter zurückgeben.
Funktionstypen
Der Funktionstyp besteht aus den Parametertypen und dem Rückgabetyp der Funktion. Für das obige Beispiel lautet der Funktionstyp:(Int, Int) -> Int
Dies kann wie folgt gelesen werden: „Eine Funktion mit zwei Parametern vom Typ Int
, die einen Wert vom Typ Int
zurückgibt.“ Funktionstyp kann als Parameter oder Rückgabetyp der Funktion festgelegt werden.
Funktionstypen können jeder Variablen wie folgt zugewiesen werden:
var mathFunction: (Int, Int) -> Int = add
Funktionen sind Sonderfälle von Schließungen. Verschlüsse nehmen eine von drei Formen an:
- Globale Funktionen: Sie haben einen Namen und können keinen Wert erfassen.
- Verschachtelte Funktionen: Sie haben einen Namen und können Werte aus ihrer umschließenden Funktion erfassen.
- Closure-Ausdrücke: Sie haben keinen Namen und können Werte aus ihrem umgebenden Kontext erfassen.
Abschlussausdruck:
Closure kann durch Einfügen eines Funktionstyps in geschweifte Klammern und des Schlüsselworts in
nach dem Rückgabetyp erstellt werden.
Kurzargumentnamen
Schließungsargumente können sich auf eine Position beziehen, z. ,
,
,
und so weiter.
Implizite Rückgaben von Closure:
Schließungen mit einem Ausdruck können implizit das Ergebnis ihres einzelnen Ausdrucks zurückgeben, indem sie das Schlüsselwort return
aus ihrer Deklaration weglassen.
Bei einem mehrzeiligen Ausdruck kann das Schlüsselwort return
nicht weggelassen werden.
Nachlaufender Verschluss:
Wenn Sie einen Abschlussausdruck an eine Funktion übergeben müssen, da das letzte Argument der Funktion und der Abschlussausdruck zu lang sind, kann er als abschließender Abschluss geschrieben werden. Ein abschließender Abschluss wird nach den Klammern () des Funktionsaufrufs geschrieben, obwohl er immer noch ein Argument für die Funktion ist. Wenn Sie die abschließende Abschlusssyntax verwenden, schreiben Sie die Argumentbezeichnung für den Abschluss nicht als Teil des Funktionsaufrufs.
Wenn closure der letzte Parameter einer Methode ist, können Sie mit Swift folgendermaßen schreiben 🖕
Die Verwendung der Trailing-Closure-Syntax kapselt die Funktionalität des Closures unmittelbar nach der von closure unterstützten Funktion sauber ein, ohne dass der gesamte Closure in die äußeren Klammern der reduce(_:)
-Methode eingeschlossen werden muss.
Werte erfassen:
Ein Closure kann Konstanten und Variablen aus dem umgebenden Kontext erfassen, in dem er definiert ist. Der Closure kann dann in seinem Body auf die Werte dieser Konstanten und Variablen verweisen und diese ändern, selbst wenn der ursprüngliche Bereich, der die Konstanten und Variablen definiert hat, nicht mehr vorhanden ist.
In Swift ist die einfachste Form eines Abschlusses, der Werte erfassen kann, eine verschachtelte Funktion, die in den Körper einer anderen Funktion geschrieben wird. Eine verschachtelte Funktion kann alle Argumente ihrer äußeren Funktion sowie alle in der äußeren Funktion definierten Konstanten und Variablen erfassen.
Diese makeIncrementer
-Funktion akzeptiert ein Argument, dh Int, als Eingabe und gibt einen Funktionstyp zurück, dh () -> Int
. Dies bedeutet, dass es eine Funktion zurückgibt, anstatt einen einfachen Wert. Die zurückgegebene Funktion hat keine Parameter und gibt bei jedem Aufruf einen Int
-Wert zurück.
Hier ist amount
Argument, runningTotal
wird als Variable deklariert und mit 0 initialisiert. Die verschachtelte Funktion incrementer
erfasst amount
und runningTotal
aus dem umgebenden Kontext.
Sehen wir makeIncrementer
in Aktion:
Hinweis: Als Optimierung kann Swift stattdessen eine Kopie eines Werts erfassen und speichern, wenn dieser Wert nicht durch einen Abschluss mutiert wird und wenn der Wert nach dem Erstellen des Abschlusses nicht mutiert wird.
Swift übernimmt auch die gesamte Speicherverwaltung, die mit der Entsorgung von Variablen verbunden ist, wenn diese nicht mehr benötigt werden.
Um den langen Schließungsausdruck im Funktionsargument loszuwerden, können Sie typealias verwenden.
Nicht-Escaping-Closures:
Closure-Parameter wurden vor Swift 3 standardmäßig escaped . Ein Abschluss würde dem Funktionskörper nicht entweichen, wenn Schließungsparameter als nicht entweichend markiert sind
In Swift 3 wurde es umgekehrt. Wenn Sie einen Abschluss als Funktionsargument übergeben, wird der Abschluss mit dem Hauptteil der Funktion ausgeführt und gibt den Compiler zurück. Wenn die Ausführung endet, verlässt der übergebene Abschluss den Gültigkeitsbereich und ist nicht mehr im Speicher vorhanden.
Das Mindeste, was Sie wissen müssen
Schließungsparameter entkommen standardmäßig nicht, wenn Sie der Schließungsausführung entkommen möchten, müssen Sie @escaping mit den Schließungsparametern verwenden.
Lebenszyklus des nicht entweichenden Verschlusses:
1. Übergeben Sie den Abschluss während des Funktionsaufrufs als Funktionsargument.
2. Führen Sie einige Arbeiten in der Funktion aus und führen Sie dann den Abschluss aus.
3. Funktion zurück.
Aufgrund einer besseren Speicherverwaltung und Optimierungen hat Swift alle Schließungen so geändert, dass sie standardmäßig nicht entkommen. CaptureList.swift
ist ein Beispiel für eine nicht entweichende Schließung.
Hinweis: @non-escaping annotation gilt nur für Funktionstypen
Escaping Closures:
Ein Closure soll einer Funktion entkommen, wenn der Closure als Argument an die Funktion übergeben wird, aber nach der Rückkehr der Funktion aufgerufen wird. Wenn Sie einen Verschluss mit @escaping
markieren, müssen Sie innerhalb des Verschlusses explizit auf self
verweisen.
Lebenszyklus der @escaping Schließung:
1. Übergeben Sie den Abschluss während des Funktionsaufrufs als Funktionsargument.
2. Machen Sie zusätzliche Arbeit in der Funktion.
3. Funktion führen Sie die Schließung asynchron oder gespeichert.
4. Funktion zurück.
Mal sehen, wo Schließungen standardmäßig sind.:
- Variablen des Funktionstyps sind implizites Escaping
- typealiases sind implizites Escaping
- Optionale Closures sind implizites Escaping
Häufiger Fehler:
Zuweisen von Nicht-Escaping-Closure zu Escaping-Closure. Es gibt 2 Möglichkeiten, dies zu beheben:
- Markieren Sie closure als escaping
- Oder behalten Sie das Standardverhalten von @noescape bei, indem Sie den closure optional
Autoclosures:
Mit Swifts @autoclosure
Attribut können Sie ein Argument definieren, das automatisch in einen closure eingeschlossen wird. Es werden keine Argumente benötigt, und wenn es aufgerufen wird, gibt es den Wert des Ausdrucks zurück, der darin eingeschlossen ist. Mit diesem syntaktischen Komfort können Sie geschweifte Klammern um den Parameter einer Funktion weglassen, indem Sie einen normalen Ausdruck anstelle eines expliziten Abschlusses schreiben.
Die Funktion assert(condition:message:file:line:)
verwendet beispielsweise eine automatische Abschaltung für die Parameter condition
und message
. Der Parameter condition
wird nur in Debug-Builds ausgewertet, und der Parameter message
wird nur ausgewertet, wenn condition
false
ist.
func assert(_ expression: @autoclosure () -> Bool,
_ message: @autoclosure () -> String) {}
Um @autoclosure
mit @escaping
Attributsyntax zu verwenden, ist:
@autoclosure @escaping () -> Bool
Verschlüsse vs Blöcke:
„Swift-Closures und Objective-C-Blöcke sind kompatibel, sodass Sie Swift-Closures an Objective-C-Methoden übergeben können, die Blöcke erwarten. Swift-Closures und -Funktionen haben denselben Typ, sodass Sie sogar den Namen einer Swift-Funktion übergeben können. Closures haben eine ähnliche Capture-Semantik wie Blöcke, unterscheiden sich jedoch in einer wichtigen Hinsicht: Variablen sind veränderbar und nicht kopierbar. Mit anderen Worten, das Verhalten von __block in Objective-C ist das Standardverhalten für Variablen in Swift.“
Closures vs Delegates:
Die Lösung hängt vom Problem ab. Darüber hinaus verlagert Apple seinen Fokus auf Rückrufmuster. UIAlertAction
ist ein Beispiel dafür.
https://medium.com/@abhimuralidharan/functional-swift-all-about-closures-310bc8af31dd
https://medium.com/@kumarpramod017/what-do-mean-escaping-and-nonescaping-closures-in-swift-d404d721f39d
https://oleb.net/blog/2016/10/optional-non-escaping-closures/
https://swiftunboxed.com/lang/closures-escaping-noescape-swift3/
https://medium.com/@johnsundell/using-autoclosure-when-designing-swift-apis-67fe20a8b2e