ARC: co to jest, zasada działania Automatic Reference Counting w iOS

Autor: IT Sectr Opublikowano: 2026-03-29 Czas czytania: 8 min

Automatic Reference Counting (ARC) — system zarządzania pamięcią w Swift i Objective-C, który automatycznie zlicza liczbę referencji do każdego obiektu i zwalnia go, gdy licznik osiągnie zero. Według Apple Swift Documentation, 2026, ARC jest wbudowany w kompilator i działa na etapie kompilacji, wstawiając wywołania retain/release w odpowiednie miejsca. W przeciwieństwie do Garbage Collection, ARC nie wymaga osobnego wątku garbage collectora i nie tworzy pauz podczas działania aplikacji.

Najważniejsze

  • ARC — Automatic Reference Counting, kompilatorowy system zarządzania pamięcią w Swift i Objective-C
  • Zasada działania — każdy obiekt ma licznik referencji (retain count), przy wyzerowaniu obiekt jest natychmiast zwalniany
  • Kwalifikatory — strong, weak i unowned określają, jak referencja wpływa na licznik i cykl życia obiektu
  • Różnica od GC — ARC działa deterministycznie na etapie kompilacji, bez pauz Stop-The-World i tła garbage collectora
  • Retain Cycle — główny problem ARC: jeśli dwa obiekty wzajemnie się referencją przez strong, ich licznik nigdy nie osiągnie zera

Co to jest ARC?

ARC (Automatic Reference Counting) — to mechanizm kompilatorowego zarządzania pamięcią, wprowadzony przez Apple w Xcode 4.2 (2011) dla Objective-C i odziedziczony przez Swift. W przeciwieństwie do ręcznego zarządzania pamięcią (Manual Retain-Release, MRR), ARC całkowicie automatyzuje wywołania retain, release i autorelease, wstawiając je na etapie kompilacji bez udziału programisty.

ARC nie jest garbage collectorem. To analiza statyczna z dynamicznym wstawianiem kodu: kompilator analizuje czas życia obiektów i umieszcza retain/release w punktach, gdzie obiekty są tworzone, kopiowane lub wychodzą z zakresu widoczności. Rezultat — deterministyczne zwalnianie pamięci: obiekt jest usuwany dokładnie w momencie, gdy przestają na niego wskazywać referencje, bez opóźnień i pauz.

Według WWDC 2011 Session 323, przejście z MRR na ARC zmniejszyło liczbę crash-bugów związanych z pamięcią o 70% w aplikacjach Apple. Programiści przestali ręcznie balansować retain/release, co wyeliminowało całą klasę wycieków i błędów double-free.

Jak działa Automatic Reference Counting

Każdy obiekt w pamięci ma licznik referencji (retain count). Przy tworzeniu obiektu licznik ustawiany jest na 1. Gdy nowa strong-referencja wskazuje na obiekt — licznik zwiększa się (retain). Gdy strong-referencja znika — licznik zmniejsza się (release). Po osiągnięciu zera obiekt jest natychmiast dealokowany.

Kompilator Swift wstawia retain/release nie przy każdym przypisaniu — używa analizy statycznej do optymalizacji. Na przykład, jeśli obiekt jest gwarantowanie nieużywany po przekazaniu, kompilator może pominąć zbędny release/retain. Ta optymalizacja nazywa się ARC Optimisation.

swift
class Person {
    let name: String
    init(name: String) {
        self.name = name
        print("\(name) zainicjalizowany (retain count: 1)")
    }
    deinit {
        print("\(name) zwolniony")
    }
}

func testARC() {
    let p = Person(name: "Alice")  // retain count = 1
    let q = p                      // retain count = 2
    // q opuszcza zakres
    // retain count = 1
    // p opuszcza zakres
    // retain count = 0 → deinit
}

W tym przykładzie widać, jak ARC zarządza licznikiem: przy przypisaniu q = p licznik zwiększa się, przy wyjściu q z zakresu — zmniejsza się. Gdy ostatnia strong-referencja znika, deinicializator jest wywoływany natychmiast. Żaden garbage collector nie czeka — pamięć jest zwalniana od razu.

ARC vs Garbage Collection: kluczowe różnice

ARC i Garbage Collection rozwiązują to samo zadanie — automatyczne zarządzanie pamięcią — ale zasadniczo różnymi podejściami. Wybór między nimi określa architekturę języka: Swift (ARC) vs Java/Go (GC). Rozważmy główne różnice.

CharakterystykaARC (Swift/ObjC)GC (Java/Go)
Moment zwolnieniaDeterministyczny: natychmiast po wyzerowaniu licznikaNiedeterministyczny: przy następnej zbiórce
Pauzy wykonaniaBrak (wstawki retain/release na etapie kompilacji)Są pauzy Stop-The-World (2–200 ms)
NarzutyInkrement/dekrement licznika przy każdej referencjiPrzechodzenie grafu obiektów, znaczniki, zwalnianie
ProblemyRetain Cycle (ręczne rozwiązywanie)Fragmentacja sterty, wycieki przy zapomnianych referencjach
Dodatkowy wątekNie wymaganyWymagany wątek garbage collectora

Kluczowy kompromis: ARC daje przewidywalny czas życia obiektów i zerowe pauzy, ale wymaga od programisty zrozumienia retain cycle i prawidłowego wyboru weak/unowned. GC uwalnia od tych obowiązków, ale kosztem niedeterministycznych pauz i dodatkowego wątku.

Strong, Weak i Unowned: kwalifikatory referencji w ARC

ARC określa trzy typy kwalifikatorów referencji, z których każdy wpływa inaczej na licznik i cykl życia obiektu. Prawidłowy wybór kwalifikatora to podstawa bezpiecznej pracy z pamięcią w Swift.

Strong

Strong — kwalifikator domyślny. Każda strong-referencja zwiększa retain count obiektu o 1. Dopóki istnieje co najmniej jedna strong-referencja, obiekt żyje. Wszystkie właściwości klas i zmienne lokalne w Swift są domyślnie strong. Strong-referencje tworzą relację własności: obiekt A jest właścicielem obiektu B.

Weak

Weak — referencja, która nie zwiększa retain count. Obiekt może być dealokowany, nawet jeśli wskazuje na niego weak-referencja. Po dealokacji weak-referencja jest automatycznie ustawiana na nil. Weak-referencje zawsze deklaruje się jako var z typem opcjonalnym (?). Używa się ich do przerywania retain cycle, szczególnie we wzorcu delegate.

Unowned

Unowned — referencja niebędąca właścicielem, która podobnie jak weak nie zwiększa retain count. Jednak unowned-referencja nie jest ustawiana na nil po dealokacji — odwołanie do zwolnionego obiektu powoduje crash. Unowned stosuje się, gdy gwarantowane jest, że obiekt żyje co najmniej tak długo jak referencjujący obiekt. Typowy scenariusz — domknięcia i relacje rodzic-dziecko z gwarantowanym czasem życia.

swift
class Customer {
    let name: String
    var card: CreditCard?         // strong
    init(name: String) { self.name = name }
    deinit { print("\(name) zwolniony") }
}

class CreditCard {
    let number: String
    unowned let customer: Customer   // unowned — nie jest właścicielem
    init(number: String, customer: Customer) {
        self.number = number
        self.customer = customer
    }
    deinit { print("Karta \(number) zwolniona") }
}

var customer: Customer? = Customer(name: "Bob")
customer?.card = CreditCard(number: "1234", customer: customer!)
customer = nil
// Customer i CreditCard oba zwolnione — brak retain cycle

Tutaj CreditCard używa unowned-referencji do Customer. Customer jest właścicielem karty (strong), a karta nie jest właścicielem klienta (unowned). Gdy Customer staje się nil, oba obiekty są zwalniane — retain cycle nie powstaje. Gdyby card.customer było strong, cykl zablokowałby zwolnienie.

Typowe problemy ARC i ich rozwiązania

Pomimo automatyzacji, ARC nie jest panaceum. Programiści napotykają kilka typowych problemów, które wymagają zrozumienia wewnętrznego mechanizmu zarządzania pamięcią.

Retain Cycle w domknięciach

Domknięcia (closures) w Swift przechwytują zmienne zewnętrzne przez strong-referencję. Jeśli domknięcie jest przypisane do właściwości klasy i przechwytuje self — powstaje retain cycle: klasa trzyma domknięcie, domknięcie trzyma self. Rozwiązanie — capture list z weak lub unowned.

swift
class NetworkManager {
    var completionHandler: ((Data?) -> Void)?
    var data: Data?

    func fetchData() {
        completionHandler = { [weak self] result in
            guard let self else { return }
            self.data = result
            self.processResult()
        }
    }

    func processResult() { }
}

Capture list [weak self] tworzy weak-referencję do self wewnątrz domknięcia. To przerywa potencjalny retain cycle. Guard let self gwarantuje, że obiekt żyje przed wykonaniem kodu. weak self — standardowa praktyka dla asynchronicznych domknięć w Swift.

Wydajność retain/release

Chociaż retain/release to lekkie operacje, w gorących pętlach częste inkrementy/dekrementy licznika powodują narzuty. W Swift 5.9+ kompilator używa optymalizacji, przy której zbędne retain/release są usuwane, jeśli analizator udowodni bezpieczeństwo. Jednak w Objective-C retain/release nadal mogą być wąskim gardłem w scenariuszach o wysokim obciążeniu z milionami wywołań na sekundę.

Autorelease Pool

Autorelease Pool — mechanizm opóźnionego release, używany w Objective-C i niektórych scenariuszach Swift. Obiekty są umieszczane w puli i otrzymują release przy drain puli. W pętlach z dużą liczbą tymczasowych obiektów (np. parsowanie JSON) utworzenie własnego autoreleasepool zmniejsza szczytowe zużycie pamięci operacyjnej.

Często zadawane pytania

Czym ARC różni się od ręcznego zarządzania pamięcią (MRR)?

Przy ręcznym zarządzaniu (MRR) programista jawnie wywoływał retain, release i autorelease. ARC wstawia te wywołania automatycznie na etapie kompilacji, eliminując ryzyko double-free, wycieków z powodu zapomnianego release i błędów balansowania retain/release.

Czy ARC może działać z kodem C/C++?

ARC zarządza tylko obiektami Objective-C i Swift class. Dla struktur i wskaźników C/C++ ARC nie jest stosowane — te obiekty są zarządzane ręcznie lub przez inteligentne wskaźniki C++ (shared_ptr, unique_ptr). Obiekty Core Foundation (CFString, CGColor) również nie podlegają ARC.

Kiedy używać weak, a kiedy unowned?

weak — gdy obiekt może być dealokowany wcześniej niż referencjujący (delegaty, asynchroniczne domknięcia). unowned — gdy obiekt gwarantowanie żyje nie krócej niż referencjujący (rodzic-dziecko, gdzie dziecko nie może istnieć bez rodzica). Jeśli nie jesteś pewien — wybierz weak.

Czym są typy egzystencjalne i jak wpływają na ARC?

Typy egzystencjalne (protocol as type) w Swift pakują wartość w specjalny kontener (existential container). Zwiększa to liczbę retain/release na granicach protokołów. W Swift 5.7+ opaque result types i parametry some zmniejszają narzuty dzięki eliminacji kontenera.

Jak sprawdzić retain count w Swift?

Nie ma bezpośredniego API do odczytu retain count w Swift — jest to uważane za szczegół implementacji. Do diagnostyki używaj Instruments (Allocations, Leaks) lub Memory Debugger w Xcode. Narzędzia te pokazują liczbę żywych instancji klasy i łańcuchy zatrzymania.

Podsumowanie

  • ARC — kompilatorowy system zarządzania pamięcią Swift i Objective-C, działający przez zliczanie referencji
  • Zasada — każdy obiekt ma retain count; przy wyzerowaniu obiekt jest zwalniany natychmiast i deterministycznie
  • Różnica od GC — ARC działa bez tła i pauz Stop-The-World, ale wymaga kontroli retain cycle
  • Strong — zwiększa licznik; weak i unowned — nie zwiększają, ale unowned nie jest zerowany przy dealokacji
  • Domknięcia — główna przyczyna retain cycle w Swift; capture list [weak self] — standardowe rozwiązanie
  • Autorelease Pool — mechanizm opóźnionego zwalniania dla tymczasowych obiektów w pętlach i niestandardowych scenariuszach
  • Diagnostyka — Xcode Memory Debugger, Instruments i LeakCanary (przez ObjC bridge) do wyszukiwania problemów

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również