Swift @escaping and Completion Handler


99

Sto cercando di capire più precisamente "Closure" di Swift.

Ma @escapinge Completion Handlersono troppo difficili da capire

Ho cercato in molti messaggi Swift e documenti ufficiali, ma sentivo che non era ancora abbastanza.

Questo è il codice di esempio di documenti ufficiali

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)

Ho sentito che ci sono due modi e motivi per utilizzare @escaping

Il primo è per memorizzare una chiusura, il secondo è per scopi operativi Async.

Le seguenti sono le mie domande :

Innanzitutto, se doSomethingviene eseguito, someFunctionWithEscapingClosureverrà eseguito con il parametro di chiusura e tale chiusura verrà salvata nell'array di variabili globali.

Penso che la chiusura sia {self.x = 100}

Come selfin {self.x = 100} quella salvata nella variabile globale completionHandlerspuò connettersi a instancequell'oggetto di SomeClass?

Secondo, capisco in someFunctionWithEscapingClosurequesto modo.

Per memorizzare la chiusura della variabile locale completionHandlernella we usingparola chiave della variabile globale 'completamentoHandlers @ escaping`!

senza restituzioni di @escapingparole chiave someFunctionWithEscapingClosure, la variabile locale completionHandlerverrà rimossa dalla memoria

@escaping è mantenere quella chiusura nella memoria

È giusto?

Infine, mi chiedo solo l'esistenza di questa grammatica.

Forse questa è una domanda molto rudimentale.

Se vogliamo che una funzione venga eseguita dopo una funzione specifica. Perché non chiamiamo solo una funzione dopo una chiamata di funzione specifica?

Quali sono le differenze tra l'utilizzo del modello precedente e l'utilizzo di una funzione di callback di escape?

Risposte:


123

Gestore del completamento rapido in fuga e non in fuga:

Come spiega Bob Lee nel suo post sul blog Completion Handlers in Swift with Bob :

Supponiamo che l'utente stia aggiornando un'app mentre la utilizza. Sicuramente vuoi avvisare l'utente quando ha finito. Forse vuoi far apparire una finestra che dice: "Congratulazioni, ora, potresti divertirti completamente!"

Quindi, come si esegue un blocco di codice solo dopo che il download è stato completato? Inoltre, come animare determinati oggetti solo dopo che un controller di visualizzazione è stato spostato al successivo? Bene, scopriremo come progettarne uno come un boss.

Sulla base del mio ampio elenco di vocaboli, i gestori di completamento stanno per

Fai le cose quando le cose sono state fatte

Il post di Bob fornisce chiarezza sui gestori di completamento (dal punto di vista di uno sviluppatore definisce esattamente ciò che dobbiamo capire).

@escaping chiusure:

Quando si passa una chiusura negli argomenti di una funzione, usandola dopo che il corpo della funzione viene eseguito e restituisce il compilatore. Quando la funzione termina, l'ambito della chiusura passata esiste e ha esistenza in memoria, finché la chiusura non viene eseguita.

Esistono diversi modi per sfuggire alla chiusura nella funzione di contenimento:

  • Archiviazione: quando è necessario memorizzare la chiusura nella variabile globale, la proprietà o qualsiasi altra memoria esistente nella memoria oltre la funzione chiamante viene eseguita e restituisce il compilatore.

  • Esecuzione asincrona: quando si esegue la chiusura in modo asincrono sulla coda di spedizione, la coda manterrà la chiusura in memoria, può essere utilizzata in futuro. In questo caso non hai idea di quando verrà eseguita la chiusura.

Quando si tenta di utilizzare la chiusura in questi scenari, il compilatore Swift mostrerà l'errore:

screenshot di errore

Per maggiore chiarezza su questo argomento puoi controllare questo post su Medium .

Aggiunta di un altro punto, che ogni sviluppatore iOS deve comprendere:

  1. Chiusura con escape: una chiusura con escape è una chiusura chiamata dopo che la funzione a cui è stata passata restituisce. In altre parole, sopravvive alla funzione a cui è stato passato.
  2. Chiusura senza escape : una chiusura chiamata all'interno della funzione in cui è stata passata, cioè prima che ritorni.

@shabhakar, cosa succede se memorizziamo una chiusura ma non la chiamiamo più tardi. O se il metodo è stato chiamato due volte ma abbiamo chiamato la chiusura solo una volta. Poiché sappiamo che il risultato è lo stesso.
user1101733

@ user1101733 Penso che tu stia parlando di sfuggire alla chiusura, Closure non verrà eseguita fino a quando non chiamerai. Nell'esempio precedente, se si chiama il metodo doSomething 2 volte 2 oggetto completamentoHandler verrà aggiunto nell'array completamentoHandlers. Se prendi il primo oggetto dall'array completamentoHandlers e lo chiami, verrà eseguito ma il conteggio dell'array completamentoHandlers rimarrà lo stesso (2).
Deepak

@ Deepak, Sì sulla chiusura della fuga. supponiamo di non utilizzare array e di utilizzare una variabile normale per memorizzare il riferimento di chiusura poiché sappiamo cosa eseguire la chiamata più recente. Riuscirà qualche memoria occupata da chiusure precedenti che non chiameranno mai?
user1101733

1
@ user1101733 Le chiusure sono di tipo riferimento (come la classe), quando si assegnano nuove chiusure alla variabile, la proprietà / variabile punterà a una nuova chiusura in modo che ARC distribuirà la memoria per le chiusure precedenti.
Deepak

28

Ecco una piccola classe di esempi che uso per ricordare a me stesso come funziona @escaping.

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

}

2
Questo codice non è corretto. Mancano le @escapingqualificazioni.
Rob il

Questo mi è piaciuto di piùi.e. escape the scope of this function.
Gal
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.