W Swift zamknięcia (closures) mogą „przechwytywać” wartości i referencje z otaczającego kontekstu. Jeśli przechwytywana wartość to typ z wartością (struct, enum), zamknięcie kopiuje dane. Jeśli przechwycona jest referencja (na przykład klasa), zamknięcie przechowuje referencję do instancji klasy, co może prowadzić do cyklu zatrzymywania, jeśli nie używa się [weak self] lub [unowned self].
Mechanizm przechwytywania pozwala realizować asynchroniczne zadania i callbacki, jednak niewłaściwe użycie utrudnia zarządzanie pamięcią.
Przykład:
class MyClass { var value = 10 func doSomething() { let closure = { [weak self] in print(self?.value ?? "nil") } closure() } }
W tym przykładzie użyto [weak self], aby uniknąć cyklu silnych referencji, ponieważ zachodzi praca asynchroniczna.
Pytanie: "Czy zawsze użycie [weak self] gwarantuje brak wycieków pamięci przy przechwytywaniu self w zamknięciu?"
Odpowiedź: Nie, jeśli w zamknięciu pojawia się silna referencja (na przykład przez zmienne pośrednie), nawet z [weak self] można uzyskać cykl zatrzymywania. Na przykład:
let closure = { [weak self] in let strongSelf = self strongSelf?.doSomething() }
Jeśli powiązać zamknięcie z długo żyjącym obiektem (na przykład NotificationCenter), można uzyskać cykl zatrzymywania, jeśli są inne referencje do self.
Historia 1
W projekcie aplikacji iOS programista zapomniał o podaniu
[weak self]podczas przekazywania zamknięcia w zapytaniu sieciowym. W rezultacie, podczas wykonywania zapytania obiekt widoku kontrolera nie był zwalniany, nawet jeśli użytkownik zamknął ekran. Doprowadziło to do wycieków pamięci przy intensywnym korzystaniu z operacji sieciowych.
Historia 2
W skomplikowanym systemie subskrypcji na timer obiekt, który subskrybował przez zamknięcie bez użycia
weak, kontynuował wykonywanie swoich działań nawet po usunięciu ekranu z pamięci. Doprowadziło to do wielokrotnych wywołań elementów UI, które już nie były w hierarchii, co powodowało awarie aplikacji.
Historia 3
Przy realizacji cache'owania danych zamknięcie przechwytywało referencję do self bez adnotacji
[unowned self]w długo żyjącym obiekcie cache. Po zniszczeniu oryginalnego obiektu, próba odwołania się do self z zamknięcia powodowała odwołanie do już zwolnionej pamięci i awarię aplikacji.