Swift @escaping und Completion Handler

98

Ich versuche, 'Closure' von Swift genauer zu verstehen.

Aber @escapingund Completion Handlersind zu schwer zu verstehen

Ich habe viele Swift-Postings und offizielle Dokumente durchsucht, aber ich hatte das Gefühl, dass es immer noch nicht genug war.

Dies ist das Codebeispiel für offizielle Dokumente

var completionHandlers: [()->Void] = []

func someFunctionWithEscapingClosure(completionHandler: @escaping ()->Void){
    completionHandlers.append(completionHandler)
}

func someFunctionWithNoneescapingClosure(closure: ()->Void){
    closure()
}

class SomeClass{
    var x:Int = 10
    func doSomething(){
        someFunctionWithEscapingClosure {
            self.x = 100
            //not excute yet
        }
        someFunctionWithNoneescapingClosure {
            x = 200
        }
    }
}

let instance = SomeClass()
instance.doSomething()
print(instance.x)

completionHandlers.first?() 
print(instance.x)

Ich habe gehört, dass es zwei Möglichkeiten und Gründe gibt @escaping

Erstens dient das Speichern eines Verschlusses, zweitens dem Async-Betrieb.

Folgendes sind meine Fragen :

Wenn doSomethingausgeführt someFunctionWithEscapingClosurewird, wird zuerst mit dem Abschlussparameter ausgeführt, und dieser Abschluss wird in einem globalen Variablenarray gespeichert.

Ich denke, dass der Abschluss {self.x = 100} ist

Wie kann selfin {self.x = 100}, das in einer globalen Variablen gespeichert completionHandlersist, eine Verbindung zu instancediesem Objekt von hergestellt werden SomeClass?

Zweitens verstehe ich das someFunctionWithEscapingClosureso.

Speichern des Schließens lokaler Variablen unter completionHandlerdem we usingSchlüsselwort 'CompletionHandlers @ Escaping ' der globalen Variablen !

Ohne @escapingSchlüsselwortrückgabe someFunctionWithEscapingClosurewird die lokale Variable completionHandleraus dem Speicher entfernt

@escaping ist, diesen Verschluss im Gedächtnis zu behalten

Ist das richtig?

Zuletzt wundere ich mich nur über die Existenz dieser Grammatik.

Vielleicht ist dies eine sehr rudimentäre Frage.

Wenn eine Funktion nach einer bestimmten Funktion ausgeführt werden soll. Warum rufen wir nicht einfach nach einem bestimmten Funktionsaufruf eine Funktion auf?

Was sind die Unterschiede zwischen der Verwendung des obigen Musters und der Verwendung einer Escape-Rückruffunktion?

Dongkun Lee
quelle

Antworten:

122

Schneller Abschluss-Handler Escaping & Non-Escaping:

Wie Bob Lee in seinem Blogbeitrag Completion Handlers in Swift with Bob erklärt :

Angenommen, der Benutzer aktualisiert eine App, während er sie verwendet. Sie möchten den Benutzer auf jeden Fall benachrichtigen, wenn dies erledigt ist. Möglicherweise möchten Sie eine Box mit der Aufschrift "Herzlichen Glückwunsch, jetzt können Sie es voll und ganz genießen!"

Wie können Sie einen Codeblock erst ausführen, nachdem der Download abgeschlossen wurde? Wie animieren Sie bestimmte Objekte erst, nachdem ein Ansichts-Controller zum nächsten verschoben wurde? Nun, wir werden herausfinden, wie man einen wie einen Chef entwirft.

Basierend auf meiner umfangreichen Vokabelliste stehen Completion Handler für

Mach Sachen, wenn die Dinge erledigt sind

Bobs Beitrag bietet Klarheit über Fertigstellungshandler (aus Entwicklersicht definiert er genau, was wir verstehen müssen).

@scaping Verschlüsse:

Wenn ein Abschluss in Funktionsargumenten übergeben wird, wird er verwendet, nachdem der Funktionskörper ausgeführt wurde und der Compiler zurückgegeben wird. Wenn die Funktion endet, existiert der Bereich des übergebenen Abschlusses und ist im Speicher vorhanden, bis der Abschluss ausgeführt wird.

Es gibt verschiedene Möglichkeiten, dem Schließen der enthaltenen Funktion zu entkommen:

  • Speicher: Wenn Sie den Abschluss in der globalen Variablen speichern müssen, wird die Eigenschaft oder ein anderer Speicher, der im Speicher nach der aufrufenden Funktion vorhanden ist, ausgeführt und der Compiler wird zurückgegeben.

  • Asynchrone Ausführung: Wenn Sie den Abschluss asynchron in der Versandwarteschlange ausführen, hält die Warteschlange den Abschluss für Sie im Speicher und kann in Zukunft verwendet werden. In diesem Fall haben Sie keine Ahnung, wann der Abschluss ausgeführt wird.

Wenn Sie versuchen, den Abschluss in diesen Szenarien zu verwenden, zeigt der Swift-Compiler den Fehler an:

Fehler Screenshot

Weitere Informationen zu diesem Thema finden Sie in diesem Beitrag auf Medium .

Hinzufügen eines weiteren Punktes, den jeder iOS-Entwickler verstehen muss:

  1. Escaping Closure : Ein Escapeing Closure ist ein Closure, der nach der Funktion aufgerufen wird, an die er zurückgegeben wurde. Mit anderen Worten, es überlebt die Funktion, an die es übergeben wurde.
  2. Nicht entkommender Abschluss : Ein Abschluss, der innerhalb der Funktion aufgerufen wird, an die er übergeben wurde, dh bevor er zurückkehrt.
Shobhakar Tiwari
quelle
@shabhakar, was ist, wenn wir einen Verschluss speichern, ihn aber später nicht aufrufen. Oder wenn die Methode zweimal aufgerufen wurde, wir aber nur einmal Closure aufgerufen haben. Da wir wissen, dass das Ergebnis das gleiche ist.
user1101733
@ user1101733 Ich denke, Sie sprechen über das Entkommen der Schließung. Closure wird erst ausgeführt, wenn Sie nicht anrufen. Im obigen Beispiel wird beim Aufrufen der doSomething-Methode 2 mal 2 das CompletionHandler-Objekt zum CompletionHandlers-Array hinzugefügt. Wenn Sie das erste Objekt aus dem CompletionHandlers-Array nehmen und aufrufen, wird es ausgeführt, aber die Anzahl der CompletionHandlers-Arrays bleibt gleich (2).
Deepak
@ Deepak, ja über Fluchtschluss. Angenommen, wir verwenden kein Array und keine normale Variable, um die Abschlussreferenz zu speichern, da wir den letzten Aufruf ausführen. Wird ein Speicher von früheren Schließungen belegt, die niemals aufgerufen werden?
user1101733
1
@ user1101733 Closure sind Referenztypen (wie die Klasse). Wenn Sie der Variablen neue Closures zuweisen, zeigt die Eigenschaft / Variable auf einen neuen Closure, sodass ARC den Speicher für frühere Closures freigibt.
Deepak
28

Hier ist eine kleine Klasse von Beispielen, mit denen ich mich daran erinnere, wie @escaping funktioniert.

class EscapingExamples: NSObject {

    var closure: (() -> Void)?

    func storageExample(with completion: (() -> Void)) {
        //This will produce a compile-time error because `closure` is outside the scope of this
        //function - it's a class-instance level variable - and so it could be called by any other method at
        //any time, even after this function has completed. We need to tell `completion` that it may remain in memory, i.e. `escape` the scope of this
        //function.
        closure = completion
        //Run some function that may call `closure` at some point, but not necessary for the error to show up.
        //runOperation()
    }

    func asyncExample(with completion: (() -> Void)) {
        //This will produce a compile-time error because the completion closure may be called at any time
        //due to the async nature of the call which precedes/encloses it.  We need to tell `completion` that it should
        //stay in memory, i.e.`escape` the scope of this function.
        DispatchQueue.global().async {
            completion()
        }
    }

    func asyncExample2(with completion: (() -> Void)) {
        //The same as the above method - the compiler sees the `@escaping` nature of the
        //closure required by `runAsyncTask()` and tells us we need to allow our own completion
        //closure to be @escaping too. `runAsyncTask`'s completion block will be retained in memory until
        //it is executed, so our completion closure must explicitly do the same.
        runAsyncTask {
            completion()
        }
    }





    func runAsyncTask(completion: @escaping (() -> Void)) {
        DispatchQueue.global().async {
            completion()
        }
    }

}
JamesK
quelle
2
Dieser Code ist nicht korrekt. Es fehlen die @escapingQualifikanten.
Rob
Das hat mir am besten gefalleni.e. escape the scope of this function.
Gal