Historia pytania:
defer został wprowadzony w Swift w celu lepszego zarządzania zasobami i wykonywania operacji po zakończeniu bloku kodu, analogicznie do finally w innych językach. Taka konstrukcja pomaga wyraźnie pokazać etapy oczyszczania lub kończenia pracy z zasobami.
Problem:
Wielu początkujących uważa, że defer od razu wykonuje zagnieżdżony kod, nie zawsze prawidłowo rozumiejąc moment wywołania. Sytuacje, w których jest wiele bloków defer, mogą być trudne do zrozumienia, jeśli nie znasz zasady LIFO (last-in, first-out). Mogą wystąpić trudności z try/catch i wczesnym wyjściem.
Rozwiązanie:
defer wykonuje zagnieżdżony kod na samym końcu zakresu, przed wyjściem z aktualnego bloku funkcji, nawet jeśli wyjście z funkcji następuje wcześniej (przez return, throw, break itp.). Kilka defer wykonuje się w odwrotnej kolejności ich wystąpienia.
Przykład kodu:
func readFile() { print("Otwórz plik") defer { print("Zamknij plik") } print("Przeczytaj linię 1") if Bool.random() { print("Wczesny powrót!") return } print("Przeczytaj linię 2") } readFile() // Na konsoli zawsze będzie: Otwórz plik, ..., Zamknij plik (na końcu)
Kluczowe cechy:
Czy można używać defer poza funkcją?
Nie, defer jest dozwolony tylko wewnątrz funkcji, metod, initializers i deinitializers. Nie można go umieścić np. w globalnej przestrzeni pliku lub wewnątrz źródła kodu poza funkcjami.
Co się stanie z defer, jeśli w bloku wystąpi wyjątek (throw)?
defer i tak zostanie wykonany. To jedna z jego zalet — zasób jest gwarantowany, że zostanie zwolniony nawet w przypadku błędu (throw). Tak realizowany jest wzorzec bezpiecznego zwalniania zasobów.
W jakiej kolejności wykonywane są różne defer?
Wykonywane są w odwrotnej kolejności (LIFO): ten defer, który został zadeklarowany później, zostanie wykonany wcześniej.
Przykład kodu:
func test() { defer { print("Pierwszy") } defer { print("Drugi") } print("Wewnątrz") } test() // Wyświetli: "Wewnątrz", "Drugi", "Pierwszy"
Funkcja z wieloma, niejednoznacznie umiejscowionymi defer; zapomniana sondaż niektórych zasobów, powracamy z funkcji w różnych miejscach. Powoduje to wycieki zasobów i trudności w debugowaniu zachowania.
Zalety: Jednolitość kodu, "zebrane" działania oczyszczania w jednym miejscu.
Wady: Trudno śledzić, co się wykona i w jakiej kolejności; mogą wystąpić wycieki przy błędach w logice.
Tworzenie jednego defer dla każdego ważnego etapu, umieszczonego w tym samym miejscu, gdzie zasób jest inicjowany, z komentarzami. Kontrola kodu przez zespół.
Zalety: Gwarantowane zwolnienie zasobów, jasne zrozumienie kolejności działań.
Wady: Wymaga dyscypliny i uwagi przy dodawaniu nowych zasobów i bloków oczyszczania.