ARC: ce este, principiul de funcționare a Automatic Reference Counting în iOS

Autor: IT Sectr Publicat: 2026-03-29 Timp de citire: 8 min

Automatic Reference Counting (ARC) — sistem de gestionare a memoriei în Swift și Objective-C care numără automat referințele către fiecare obiect și îl eliberează atunci când contorul ajunge la zero. Potrivit Apple Swift Documentation, 2026, ARC este încorporat în compilator și funcționează în etapa de compilare, inserând apeluri retain/release în locurile potrivite. Spre deosebire de Garbage Collection, ARC nu necesită un fir separat de colectare și nu creează pauze în timpul execuției aplicației.

Puncte cheie

  • ARC — Automatic Reference Counting, sistem de gestionare a memoriei în Swift și Objective-C
  • Principiul de funcționare — fiecare obiect are un contor de referințe (retain count), la atingerea zeroului obiectul este imediat eliberat
  • Calificatorii — strong, weak și unowned determină cum afectează referința contorul și ciclul de viață al obiectului
  • Diferența față de GC — ARC funcționează determinist în etapa de compilare, fără pauze Stop-The-World și fir de fundal de colectare
  • Retain Cycle — principala problemă ARC: dacă două obiecte se referă reciproc prin strong, contorul lor nu ajunge niciodată la zero

Ce este ARC?

ARC (Automatic Reference Counting) — este un mecanism de gestionare a memoriei la nivel de compilator, introdus de Apple în Xcode 4.2 (2011) pentru Objective-C și moștenit de Swift. Spre deosebire de gestionarea manuală a memoriei (Manual Retain-Release, MRR), ARC automatizează complet apelurile retain, release și autorelease, inserându-le în etapa de compilare fără implicarea dezvoltatorului.

ARC nu este un garbage collector. Este o analiză statică cu inserare dinamică de cod: compilatorul analizează durata de viață a obiectelor și plasează retain/release în punctele în care obiectele sunt create, copiate sau ies din domeniul de vizibilitate. Rezultatul — eliberare deterministică a memoriei: obiectul este șters exact în momentul în care nu mai este referit, fără întârzieri și pauze.

Potrivit WWDC 2011 Session 323, trecerea de la MRR la ARC a redus numărul de bug-uri de crash legate de memorie cu 70% în aplicațiile Apple. Dezvoltatorii au încetat să mai echilibreze manual retain/release, ceea ce a eliminat o întreagă clasă de scurgeri și erori double-free.

Cum funcționează Automatic Reference Counting

Fiecare obiect în memorie are un contor de referințe (retain count). La crearea obiectului, contorul este setat la 1. Când o nouă referință strong indică spre obiect — contorul crește (retain). Când referința strong dispare — contorul scade (release). La atingerea zeroului, obiectul este imediat dealocat.

Compilatorul Swift nu inserează retain/release la fiecare atribuire — folosește analiza statică pentru optimizare. De exemplu, dacă un obiect este garantat că nu va fi folosit după transfer, compilatorul poate omite release/retain în plus. Această optimizare se numește ARC Optimisation.

swift
class Person {
    let name: String
    init(name: String) {
        self.name = name
        print("\(name) inițializat (retain count: 1)")
    }
    deinit {
        print("\(name) dealocat")
    }
}

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

În acest exemplu se vede cum ARC gestionează contorul: la atribuirea q = p, contorul crește, la ieșirea lui q din domeniu — scade. Când ultima referință strong dispare, deinitializatorul este apelat imediat. Niciun garbage collector nu așteaptă — memoria este eliberată imediat.

ARC vs Garbage Collection: diferențe cheie

ARC și Garbage Collection rezolvă aceeași sarcină — gestionarea automată a memoriei — dar cu abordări fundamental diferite. Alegerea dintre ele determină arhitectura limbajului: Swift (ARC) vs Java/Go (GC). Să examinăm diferențele principale.

CaracteristicăARC (Swift/ObjC)GC (Java/Go)
Momentul eliberăriiDeterminist: imediat la atingerea zerouluiNedeterminist: la următoarea colectare
Pauze de execuțieNu (inserări retain/release în etapa de compilare)Există pauze Stop-The-World (2–200 ms)
Costuri suplimentareIncrementare/decrementare contor la fiecare referințăParcurgerea grafului de obiecte, marcare, eliberare
ProblemeRetain Cycle (rezolvare manuală)Fragmentarea heap-ului, scurgeri la referințe uitate
Fir suplimentarNu este necesarNecesită un fir de garbage collector

Compromisul cheie: ARC oferă o durată de viață previzibilă a obiectelor și pauze zero, dar necesită înțelegerea retain cycle și alegerea corectă weak/unowned. GC eliberează de aceste griji, dar cu prețul pauzelor nedeterministe și al unui fir suplimentar.

Strong, Weak și Unowned: calificatorii de referință în ARC

ARC definește trei tipuri de calificatori de referință, fiecare afectând diferit contorul și ciclul de viață al obiectului. Alegerea corectă a calificatorului este baza funcționării sigure cu memoria în Swift.

Strong

Strong — calificatorul implicit. Fiecare referință strong crește retain count al obiectului cu 1. Cât timp există cel puțin o referință strong, obiectul este viu. Toate proprietățile claselor și variabilele locale în Swift sunt strong implicit. Referințele strong creează o relație de proprietate: obiectul A deține obiectul B.

Weak

Weak — referință care nu crește retain count. Obiectul poate fi dealocat chiar dacă o referință weak indică spre el. După dealocare, referința weak este automat setată la nil. Referințele weak sunt întotdeauna declarate ca var cu tip opțional (?). Sunt folosite pentru a sparge retain cycle, în special în pattern-ul delegate.

Unowned

Unowned — referință care nu deține obiectul și, ca și weak, nu crește retain count. Cu toate acestea, referința unowned nu este setată la nil după dealocare — accesarea obiectului eliberat provoacă un crash. Unowned se folosește când este garantat că obiectul trăiește cel puțin cât obiectul care face referință. Scenariul tipic — închideri (closures) și relații părinte-copil cu durată de viață garantată.

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

class CreditCard {
    let number: String
    unowned let customer: Customer   // unowned — nu deține
    init(number: String, customer: Customer) {
        self.number = number
        self.customer = customer
    }
    deinit { print("Card \(number) dealocat") }
}

var customer: Customer? = Customer(name: "Bob")
customer?.card = CreditCard(number: "1234", customer: customer!)
customer = nil
// Customer și CreditCard ambele eliberate — fără retain cycle

Aici CreditCard folosește o referință unowned către Customer. Customer deține cardul (strong), iar cardul nu deține clientul (unowned). Când Customer devine nil, ambele obiecte sunt eliberate — retain cycle nu apare. Dacă card.customer ar fi fost strong, ciclul ar fi blocat eliberarea.

Probleme tipice ARC și soluțiile lor

În ciuda automatizării, ARC nu este un panaceu. Dezvoltatorii întâmpină câteva probleme tipice care necesită înțelegerea mecanismului intern de gestionare a memoriei.

Retain Cycle în închideri

Închiderile (closures) în Swift captează variabilele externe prin referință strong. Dacă o închidere este atribuită unei proprietăți de clasă și capturează self — apare retain cycle: clasa deține închiderea, închiderea deține self. Soluția — capture list cu weak sau 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] creează o referință weak la self în interiorul închiderii. Aceasta sparge potențialul retain cycle. Guard let self garantează că obiectul este viu înainte de executarea codului. weak self — practică standard pentru închideri asincrone în Swift.

Performanța retain/release

Deși retain/release sunt operații ușoare, în bucle îndense, incrementările/decrementările frecvente ale contorului generează costuri suplimentare. În Swift 5.9+, compilatorul folosește optimizare, prin care retain/release în plus sunt eliminate dacă analizatorul demonstrează siguranța. Totuși, în Objective-C, retain/release pot fi încă un blocaj în scenariile cu încărcare mare cu milioane de apeluri pe secundă.

Autorelease Pool

Autorelease Pool — mecanism de release întârziat, folosit în Objective-C și unele scenarii Swift. Obiectele sunt plasate într-un pool și primesc release la drain-ul pool-ului. În buclele cu un număr mare de obiecte temporare (de exemplu, parsare JSON), crearea propriului autoreleasepool reduce consumul de vârf al memoriei operative.

Întrebări frecvente

Cu ce se deosebește ARC de gestionarea manuală a memoriei (MRR)?

În gestionarea manuală (MRR), dezvoltatorul apela explicit retain, release și autorelease. ARC inserează aceste apeluri automat în etapa de compilare, eliminând riscul de double-free, scurgeri din cauza unui release uitat și erori de echilibrare retain/release.

Poate ARC să lucreze cu cod C/C++?

ARC gestionează doar obiecte Objective-C și clase Swift. Pentru structuri și pointeri C/C++, ARC nu se aplică — aceste obiecte sunt gestionate manual sau prin pointeri inteligenți C++ (shared_ptr, unique_ptr). Obiectele Core Foundation (CFString, CGColor) nici ele nu sunt acoperite de ARC.

Când să folosim weak și când unowned?

weak — când obiectul poate fi dealocat înaintea obiectului care face referință (delegate, închideri asincrone). unowned — când este garantat că obiectul trăiește cel puțin cât cel care face referință (părinte-copil, unde copilul nu poate exista fără părinte). Dacă nu sunteți sigur — alegeți weak.

Ce sunt tipurile existențiale și cum afectează ARC?

Tipurile existențiale (protocol as type) în Swift împachetează valoarea într-un container special (existential container). Acest lucru crește numărul de retain/release la granițele protocoalelor. În Swift 5.7+, opaque result types și parametrii some reduc costurile prin eliminarea containerului.

Cum verific retain count în Swift?

Nu există o API directă pentru citirea retain count în Swift — acesta este considerat un detaliu de implementare. Pentru diagnosticare, utilizați Instruments (Allocations, Leaks) sau Memory Debugger în Xcode. Aceste instrumente arată numărul de instanțe vii ale clasei și lanțurile de reținere.

Rezumat

  • ARC — sistem de gestionare a memoriei la nivel de compilator pentru Swift și Objective-C, bazat pe numărarea referințelor
  • Principiu — fiecare obiect are retain count; la atingerea zeroului, obiectul este eliberat imediat și determinist
  • Diferența față de GC — ARC funcționează fără fir de fundal și pauze Stop-The-World, dar necesită controlul retain cycle
  • Strong — crește contorul; weak și unowned — nu cresc, dar unowned nu se anulează la dealocare
  • Închideri — principala cauză a retain cycle în Swift; capture list [weak self] — soluția standard
  • Autorelease Pool — mecanism de eliberare întârziată pentru obiecte temporare în bucle și scenarii personalizate
  • Diagnosticare — Xcode Memory Debugger, Instruments și LeakCanary (prin ObjC bridge) pentru găsirea problemelor

Vom dezvolta o aplicație mobilă la cheie

IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.

Discutați proiectul

Citiți și