programowanieProgramista iOS

Wyjaśnij mechanizm automatycznego przekazywania ownership (Automatic Reference Counting, ARC) między obiektami w Swift oraz jak to się wiąże z zarządzaniem pamięcią?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź.

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:

  • Zarządzanie pamięcią jest przezroczyste dla programisty.
  • Wsparcie tylko dla klas, struct i enumy nie są śledzone przez odniesienia.
  • Aby rozwiązać retain cycle, używa się weak i unowned.

Pytania z podstępem.

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 }

Typowe błędy i antywzorce

  • Używanie silnych odniesień tam, gdzie potrzebne są weak/unowned.
  • Zapominanie o ARC podczas pracy z klasami i tablicami/słownikami zawierającymi obiekty.
  • Próba ręcznego zwolnienia pamięci typów wartości lub używanie weak dla struct/enum.

Przykład z życia

Negatywny przypadek

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.

Pozytywny przypadek

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.