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) — 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.
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.
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 ș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ării | Determinist: imediat la atingerea zeroului | Nedeterminist: la următoarea colectare |
| Pauze de execuție | Nu (inserări retain/release în etapa de compilare) | Există pauze Stop-The-World (2–200 ms) |
| Costuri suplimentare | Incrementare/decrementare contor la fiecare referință | Parcurgerea grafului de obiecte, marcare, eliberare |
| Probleme | Retain Cycle (rezolvare manuală) | Fragmentarea heap-ului, scurgeri la referințe uitate |
| Fir suplimentar | Nu este necesar | Necesită 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.
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 — 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 — 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 — 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ă.
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.
Î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.
Î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.
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.
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 — 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
Î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.
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.
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.
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.
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
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.
Citiți și