Historia pytania:
Wraz z rozwojem Objective-C i pojawieniem się Swift w Apple pojawił się problem automatycznego i bezpiecznego zarządzania pamięcią aplikacji bez jawnej pracy programisty z komendami retain/release. Swift wykorzystuje Automatic Reference Counting (ARC), który pozwala na śledzenie liczby odniesień do obiektów klasowych i automatyczne zwalnianie pamięci, gdy ostatni właściciel znika.
Problem:
Ponieważ Swift jest językiem wieloparadygmatowym, obsługuje zarówno typy wartości (struct, enum), jak i typy referencyjne (class). Dla tych ostatnich ważne jest, aby pamięć była zwalniana na czas, w przeciwnym razie może wystąpić wyciek pamięci. Sytuacja staje się skomplikowana w przypadku retain cycle (cykliczne odniesienia), kiedy dwa obiekty odnoszą się do siebie, uniemożliwiając ARC zwolnienie pamięci.
Rozwiązanie:
Swift stosuje ARC tylko do obiektów klasowych. Gdy liczba odniesień spada do 0 – pamięć jest zwalniana. Aby rozwiązać problem retain cycle, używane są słowa kluczowe weak/unowned.
Przykład kodu:
class Person { var name: String var pet: Pet? init(name: String) { self.name = name } deinit { print("\(name) jest de-inicjalizowany") } } class Pet { var owner: Person? init() {} deinit { print("Pet jest de-inicjalizowany") } } var tom: Person? = Person(name: "Tom") var cat: Pet? = Pet() tom!.pet = cat cat!.owner = tom tom = nil cat = nil // Oba obiekty NIE są zwalniane, występuje retain cycle
Kluczowe cechy:
Czy można stosować ARC do typów wartości?
Nie, ARC śledzi tylko odniesienia do obiektów klasowych. Struktury i enumy są przekazywane przez wartość, a Swift automatycznie zwalnia ich pamięć (zazwyczaj po wyjściu z zakresu).
Co się stanie, jeśli nie użyjesz weak/unowned w właściwościach dwóch wzajemnie odnoszących się obiektów?
Wystąpi cykliczne odniesienie (retain cycle), przez co ARC nigdy nie zwolni pamięci zajmowanej przez te obiekty. Taki kod doprowadzi do pamięciowego wycieku — zawsze używaj weak (lub unowned) dla przynajmniej jednej strony takiego odniesienia.
Po co potrzebny jest unowned, skoro jest weak?
unowned jest potrzebny w przypadkach, gdy odniesienie zawsze musi istnieć w czasie życia innego obiektu i nigdy nie powinno stawać się nil. weak — to odniesienie do zera, unowned — to odniesienie niezerowe, ale bez zwiększania licznika retain.
Przykład kodu:
class A { var b: B? } class B { unowned var a: A // nigdy nie nil w czasie życia B }
Dwa kontrolery zawierają wzajemne silne odniesienia: jeden — przez właściwość delegata, drugi — przez tablicę kontrolerów potomnych. Twórca nie używa weak u delegata.
Zalety: Błędy nie są widoczne od razu, wszystko "działa" na pierwszy rzut oka.
Wady: Wraz ze wzrostem objętości aplikacji pamięć nie jest zwalniana, pojawiają się wycieki; przy pracy z ograniczoną ilością pamięci mogą wystąpić awarie.
Delegat i obserwator zdarzeń są konfigurowane jako weak, tablica kontrolerów jest czyszczona w miarę znikania kontrolerów. Wszystko jest starannie udokumentowane.
Zalety: Brak wycieków, wszystkie obiekty są zwalniane na czas, brak utraty wydajności.
Wady: Możliwe jest nagłe utracenie delegata (jeśli gdzieś wyżej zapomniano o silnym odniesieniu); wymaga to staranności przy architekturze aplikacji.