programowanieProgramista iOS

Opisz mechanizm działania defer w Swift, na jakie przypadki graniczne i cechy warto zwrócić uwagę?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź.

defer to specjalna konstrukcja w Swift, która pozwala na wykonanie określonego bloku kodu bezpośrednio przed wyjściem z zakresu (scope) funkcji, niezależnie od tego, czy wychodzimy z niej przez zwykły return, czy przez błąd (throw). Defer jest wygodny do użycia w celu zwolnienia zasobów, anulowania zmian lub finalizacji operacji.

Cechy działania:

  • Jeśli w funkcji jest kilka defer, zostaną one wykonane w odwrotnej kolejności (struktura stosu LIFO — last in, first out).
  • defer wykonuje się nawet w przypadku wystąpienia błędu (throw) lub przy każdym return z funkcji.
  • W closure defer odnosi się do ciała closure, a nie miejsca wywołania closure.

Przykład:

func testDefer() { print("początek") defer { print("pierwszy defer") } defer { print("drugi defer") } print("koniec") } // Wydrukuje: // początek // koniec // drugi defer // pierwszy defer

Przypadki graniczne:

  • defer przechwytuje wartości zmiennych w momencie powstania defer: jeśli w closure defer używamy zmiennych, ich zmiany będą widoczne w momencie wykonania defer.
  • Jeśli funkcja zakończyła się wykonaniem z fatalError — defer nie zostanie wywołany.

Pytanie z podstępem.

Czy wszystkie bloki defer w funkcji zostaną wykonane, jeśli wewnątrz niej wywołamy fatalError?

Odpowiedź: Nie, jeśli w funkcji wywołano fatalError (lub podobne niekontrolowane awarie), wszystkie opóźnione bloki przez defer nie zostaną wykonane. defer nie gwarantuje wywołania kodu w przypadkach awaryjnego zakończenia działania aplikacji.

Przykład:

func foo() { defer { print("Defer 1") } fatalError("Ups") defer { print("Defer 2") } } foo() // nic nie wydrukuje, nastąpi awaria

Przykłady rzeczywistych błędów z powodu nieznajomości szczegółów tematu.


Historia

W projekcie używano defer do zamknięcia deskryptora pliku. W przypadku wystąpienia błędu używano fatalError zamiast poprawnego throw, co prowadziło do wycieku otwartego zasobu, ponieważ defer nie działał przy awarii.


Historia

W jednej funkcji było kilka defer, z których niektóre zależały od stanu zmiennych lokalnych. Programista oczekiwał, że zmienna będzie miała specyficzną wartość, ale ta wartość w defer zmieniła się przez inny fragment kodu, i podczas wykonywania defer użyto aktualnej, a nie starej wartości, co prowadziło do błędu anulowania transakcji nie z odpowiednim ID.


Historia

W zagnieżdżonym closure napisano blok defer, sądząc, że zostanie on wykonany przy wyjściu z zewnętrznej funkcji. W rezultacie ten defer został wykonany przy wyjściu z closure, a nie całej funkcji, i zasób został zwolniony zbyt wcześnie.