Swift @escaping and Completion Handler

100

Próbuję dokładniej zrozumieć „Zamknięcie” Swifta.

Ale @escapingi Completion Handlersą zbyt trudne do zrozumienia

Przeszukałem wiele ogłoszeń Swift i oficjalnych dokumentów, ale czułem, że to wciąż za mało.

To jest przykład kodu oficjalnych dokumentów

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)

Słyszałem, że są dwa sposoby i powody używania @escaping

Pierwszy służy do przechowywania zamknięcia, a drugi do celów operacyjnych Async.

Oto moje pytania :

Po pierwsze, jeśli doSomethingWykonuje wtedy someFunctionWithEscapingClosurebędzie wykonanie z parametrem zamknięcia i zamknięcie zostanie zapisany w globalnej tablicy zmiennych.

Myślę, że zamknięcie to {self.x = 100}

Jak selfw {self.x = 100} ta zapisana w zmiennej globalnej completionHandlersmoże połączyć się z instancetym obiektem SomeClass?

Po drugie, rozumiem w someFunctionWithEscapingClosureten sposób.

Aby przechowywać lokalne zamknięcie zmiennej completionHandlerw globalnym we usingsłowie kluczowym „completeHandlers @ escaping”!

bez zwracania @escapingsłów kluczowych someFunctionWithEscapingClosurezmienna lokalna completionHandlerzostanie usunięta z pamięci

@escaping to zachować to zamknięcie w pamięci

Czy to jest poprawne?

Na koniec po prostu zastanawiam się nad istnieniem tej gramatyki.

Może to bardzo podstawowe pytanie.

Jeśli chcemy, aby jakaś funkcja była wykonywana po określonej funkcji. Dlaczego po prostu nie wywołamy jakiejś funkcji po wywołaniu określonej funkcji?

Jakie są różnice między użyciem powyższego wzorca a użyciem funkcji ucieczki zwrotnej?

Dongkun Lee
źródło

Odpowiedzi:

124

Ucieczka programu obsługi szybkiego zakończenia i brak ucieczki:

Jak wyjaśnia Bob Lee w swoim poście na blogu Completion Handlers in Swift with Bob :

Załóżmy, że użytkownik aktualizuje aplikację podczas jej używania. Na pewno chcesz powiadomić użytkownika, gdy to się skończy. Być może chcesz wyświetlić okno z napisem „Gratulacje, teraz możesz w pełni się cieszyć!”

Jak więc uruchomić blok kodu dopiero po zakończeniu pobierania? Co więcej, w jaki sposób animować pewne obiekty dopiero po przeniesieniu kontrolera widoku do następnego? Cóż, dowiemy się, jak zaprojektować takiego jak szefa.

Opierając się na mojej obszernej liście słownictwa, pomocnicy uzupełniania oznaczają

Rób rzeczy, gdy coś już zostało zrobione

Post Boba zapewnia jasność co do programów obsługi zakończenia (z punktu widzenia programisty dokładnie definiuje to, co musimy zrozumieć).

@escaping closures:

Kiedy przekazuje się zamknięcie w argumentach funkcji, używa się go po wykonaniu treści funkcji i zwraca kompilator z powrotem. Kiedy funkcja się kończy, zakres przekazanego domknięcia istnieje i istnieje w pamięci, dopóki zamknięcie nie zostanie wykonane.

Istnieje kilka sposobów na uniknięcie zamknięcia w funkcji zawierającej:

  • Przechowywanie: gdy chcesz zapisać zamknięcie w zmiennej globalnej, właściwości lub innym miejscu w pamięci, która istniała w przeszłości funkcji wywołującej, zostanie wykonana i zwróć kompilator.

  • Wykonywanie asynchroniczne: gdy wykonujesz zamknięcie asynchronicznie w kolejce wysyłkowej, kolejka będzie przechowywać zamknięcie w pamięci, aby można było z niej korzystać w przyszłości. W takim przypadku nie masz pojęcia, kiedy zamknięcie zostanie wykonane.

Podczas próby użycia zamknięcia w tych scenariuszach kompilator Swift wyświetli błąd:

zrzut ekranu błędu

Aby uzyskać więcej informacji na ten temat, sprawdź ten post na Medium .

Dodanie jeszcze jednego punktu, które każdy programista iOS musi zrozumieć:

  1. Zamknięcie ucieczki: Zamknięcie ucieczki to zamknięcie wywoływane po funkcji, do której została przekazana. Innymi słowy, przeżywa funkcję, do której został przekazany.
  2. Zamknięcie bez ucieczki : zamknięcie, które jest wywoływane w funkcji, do której zostało przekazane, tj. Przed powrotem.
Shobhakar Tiwari
źródło
@shabhakar, co jeśli zapiszemy zamknięcie, ale nie będziemy go później wywoływać. Lub jeśli metoda została wywołana dwa razy, ale tylko raz wywołaliśmy zamknięcie. Ponieważ wiemy, że wynik jest taki sam.
user1101733
@ user1101733 Myślę, że mówisz o ucieczce przed zamknięciem. Zamknięcie nie zostanie wykonane, dopóki nie zadzwonisz. W powyższym przykładzie, jeśli wywołasz metodę doSomething 2 razy 2 obiekt completeHandler doda do tablicy completeHandlers. Jeśli weźmiesz pierwszy obiekt z tablicy CompleteHandlers i wywołasz go, zostanie wykonany, ale liczba tablic CompleteHandlers pozostanie taka sama (2).
Deepak
@Deepak, tak o zamknięciu ewakuacyjnym. załóżmy, że nie używamy tablicy i używamy normalnej zmiennej do przechowywania odniesienia do zamknięcia, ponieważ wykonujemy ostatnie wywołanie. Czy trochę pamięci zajmie poprzednie zamknięcia, które nigdy nie zadzwonią?
user1101733
1
@ user1101733 Zamknięcia są typami referencyjnymi (jak klasa), kiedy przypiszesz nowe domknięcia do zmiennej, właściwość / zmienna wskaże nowe zamknięcie, więc ARC zwolni pamięć dla poprzednich zamknięć.
Deepak
28

Oto mała klasa przykładów, których używam, aby przypomnieć sobie, jak działa @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()
        }
    }

}
JamesK
źródło
2
Ten kod jest nieprawidłowy. Brakuje @escapingkwalifikacji.
Rob
Najbardziej podobało mi się toi.e. escape the scope of this function.
Gal