programowanieProgramista iOS

Wyjaśnij działanie zamknięć (closures) w Swift: różnice w stosunku do funkcji, cechy przechwytywania zmiennych, możliwe problemy przy niewłaściwym użyciu.

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź

Zamknięcia (closures) w Swift to samodzielne bloki kodu, które mogą przechwytywać i przechowywać odniesienia do zmiennych i stałych z otaczającego kontekstu. Umożliwiają organizację callbacków, asynchroniczne przetwarzanie oraz przechowywanie wykonania kodu jako wartości zmiennych.

Różnice w stosunku do funkcji

  • Zamknięcia mogą przechwytywać zmienne z zewnętrznego kontekstu.
  • Mają bardziej zwięzłą składnię.
  • Mogą być przekazywane jako wartości.

Przykład zamknięcia:

var counter = 0 let incrementer: () -> Void = { counter += 1 } incrementer() print(counter) // 1

Cechy przechwytywania

  • Domyślnie zmienne są przechwytywane strong (przez mocne odniesienie).
  • Można jawnie definiować zasady przechwytywania za pomocą listy przechwytywania ([weak self], [unowned self]).

Problemy

  • Głównym problemem są potencjalne cykle zatrzymywania przy przechwytywaniu self wewnątrz zamknięcia.
  • W kodzie wielowątkowym można nieumyślnie przechwycić przestarzałe wartości zmiennych.

Pytanie z haczykiem

Czym różni się strong-capture od użycia [weak self] lub [unowned self] w liście przechwytywania zamknięcia? Jakie to rodzi konsekwencje?

Odpowiedź: Jeśli nie wskażesz [weak self] lub [unowned self], zamknięcie domyślnie przechwyci self przez mocne odniesienie, co doprowadzi do cyklu zatrzymywania, jeśli zamknięcie i self będą odnosić się do siebie nawzajem. [weak self] przechwytuje self przez słabe odniesienie (opcjonalne), [unowned self] — przez niekontrolowane odniesienie (nieopcjonalne). Niewłaściwy wybór lub brak listy przechwytywania prowadzi do wycieków pamięci lub awarii.

class Test { var closure: (() -> Void)? func setup() { closure = { [weak self] in self?.doWork() } } func doWork() { ... } }

Przykłady rzeczywistych błędów wynikających z nieznajomości szczegółów tematu


Historia

Programista nie użył listy przechwytywania w zamknięciu w UIViewController, który uruchamiał asynchroniczne ładowanie danych. W rezultacie controller i jego widok nie były zwalniane, co prowadziło do wycieku pamięci po zamknięciu (dismiss). Analiza infrastruktury wykazała setki "utknętych" w pamięci kontrolerów.


Historia

Zamknięcie przechwyciło self za pomocą [unowned self], ale cykl życia self zakończył się wcześniej niż zamknięcia. Powstała awaria przy próbie dostępu do już zwolnionego obiektu — aplikacja się zawieszała przy próbie pracy z self wewnątrz zamknięcia.


Historia

W projekcie wewnątrz zagnieżdżonego zamknięcia przechwycono dwie zmienne: jedną przez strong, drugą przez weak. Okazało się, że weak-odniesienie było zerowane zbyt wcześnie, a dane potrzebne do działania zamknięcia zostały utracone — błąd ujawnił się tylko w teście obciążeniowym w środowisku wielowątkowym.