Automatic Reference Counting (ARC) è un sistema di gestione della memoria in Swift e Objective-C che conta automaticamente il numero di riferimenti a ciascun oggetto e lo rilascia quando il contatore raggiunge lo zero. Secondo la Documentazione Apple Swift, 2026, ARC è integrato nel compilatore e funziona in fase di compilazione, inserendo chiamate retain/release nei punti appropriati. A differenza del Garbage Collection, ARC non richiede un thread di raccolta separato e non crea pause durante l'esecuzione dell'applicazione.
Punti chiave
ARC (Automatic Reference Counting) è un meccanismo di gestione della memoria basato sul compilatore introdotto da Apple in Xcode 4.2 (2011) per Objective-C ed ereditato da Swift. A differenza della gestione manuale della memoria (Manual Retain-Release, MRR), ARC automatizza completamente le chiamate retain, release e autorelease, inserendole in fase di compilazione senza intervento dello sviluppatore.
ARC non è un garbage collector. È analisi statica con inserimento dinamico di codice: il compilatore analizza la durata degli oggetti e posiziona retain/release nei punti in cui gli oggetti vengono creati, copiati o escono dall'ambito. Il risultato è un rilascio deterministico della memoria: l'oggetto viene rimosso esattamente quando non ci sono più riferimenti che puntano ad esso, senza ritardi o pause.
Secondo la WWDC 2011 Session 323, il passaggio da MRR ad ARC ha ridotto i bug di crash legati alla memoria del 70% nelle applicazioni Apple. Gli sviluppatori hanno smesso di bilanciare manualmente retain/release, eliminando un'intera classe di perdite ed errori double-free.
Ogni oggetto in memoria ha un contatore di riferimenti (retain count). Quando un oggetto viene creato, il contatore viene impostato a 1. Quando un nuovo riferimento strong punta all'oggetto, il contatore aumenta (retain). Quando un riferimento strong scompare, il contatore diminuisce (release). Al raggiungimento dello zero, l'oggetto viene immediatamente rilasciato.
Il compilatore Swift non inserisce retain/release ad ogni assegnazione — utilizza l'analisi statica per l'ottimizzazione. Ad esempio, se è garantito che un oggetto non verrà utilizzato dopo essere stato passato, il compilatore può saltare un release/retain non necessario. Questa ottimizzazione si chiama ARC Optimization.
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name) initialized (retain count: 1)")
}
deinit {
print("\(name) deallocated")
}
}
func testARC() {
let p = Person(name: "Alice") // retain count = 1
let q = p // retain count = 2
// q esce dall'ambito
// retain count = 1
// p esce dall'ambito
// retain count = 0 → deinit
}
Questo esempio mostra come ARC gestisce il contatore: assegnando q = p, il contatore aumenta; quando q esce dall'ambito, diminuisce. Quando l'ultimo riferimento strong scompare, il deinizializzatore viene chiamato immediatamente. Nessun garbage collector attende — la memoria viene liberata all'istante.
ARC e Garbage Collection risolvono lo stesso problema — la gestione automatica della memoria — ma con approcci fondamentalmente diversi. La scelta tra di essi definisce l'architettura del linguaggio: Swift (ARC) vs Java/Go (GC). Esaminiamo le differenze principali.
| Caratteristica | ARC (Swift/ObjC) | GC (Java/Go) |
|---|---|---|
| Momento del rilascio | Deterministico: immediatamente quando il contatore arriva a zero | Non deterministico: al prossimo ciclo di raccolta |
| Pause di esecuzione | Nessuna (retain/release inseriti in compilazione) | Pause Stop-The-World (2–200 ms) |
| Overhead | Incremento/decremento del contatore ad ogni riferimento | Attraversamento del grafo oggetti, marcatura, spazzamento |
| Problemi | Retain Cycle (risoluzione manuale) | Frammentazione dell'heap, perdite da riferimenti dimenticati |
| Thread aggiuntivo | Non richiesto | Thread del garbage collector richiesto |
Il compromesso chiave: ARC fornisce durate di vita prevedibili e zero pause, ma richiede che lo sviluppatore comprenda i retain cycle e scelga correttamente weak/unowned. GC libera lo sviluppatore da queste preoccupazioni, ma al costo di pause non deterministiche e un thread aggiuntivo.
ARC definisce tre tipi di qualificatori di riferimento, ciascuno influenzando il contatore e il ciclo di vita dell'oggetto in modo diverso. Scegliere il qualificatore corretto è la base per una gestione sicura della memoria in Swift.
Strong è il qualificatore predefinito. Ogni riferimento strong aumenta il retain count dell'oggetto di 1. Finché esiste almeno un riferimento strong, l'oggetto rimane vivo. Tutte le proprietà di classe e le variabili locali in Swift sono strong per impostazione predefinita. I riferimenti strong creano una relazione di possesso: l'oggetto A possiede l'oggetto B.
Weak è un riferimento che non aumenta il retain count. Un oggetto può essere rilasciato anche se un riferimento weak punta ad esso. Dopo il rilascio, il riferimento weak viene automaticamente impostato a nil. I riferimenti weak sono sempre dichiarati come var con tipo opzionale (?). Sono usati per rompere i retain cycle, specialmente nel pattern delegate.
Unowned è un riferimento non possessivo che, come weak, non aumenta il retain count. Tuttavia, un riferimento unowned non viene impostato a nil dopo il rilascio — accedere a un oggetto rilasciato causa un crash. Unowned viene usato quando è garantito che l'oggetto viva almeno quanto l'oggetto referenziante. I casi tipici sono le closures e le relazioni genitore-figlio con durata di vita garantita.
class Customer {
let name: String
var card: CreditCard? // strong
init(name: String) { self.name = name }
deinit { print("\(name) deallocated") }
}
class CreditCard {
let number: String
unowned let customer: Customer // unowned — non possiede
init(number: String, customer: Customer) {
self.number = number
self.customer = customer
}
deinit { print("Card \(number) deallocated") }
}
var customer: Customer? = Customer(name: "Bob")
customer?.card = CreditCard(number: "1234", customer: customer!)
customer = nil
// Customer e CreditCard entrambi rilasciati — nessun retain cycle
Qui CreditCard usa un riferimento unowned a Customer. Customer possiede la carta (strong), e la carta non possiede il cliente (unowned). Quando Customer viene rilasciato, entrambi gli oggetti vengono rilasciati — nessun retain cycle si verifica. Se card.customer fosse strong, il ciclo bloccherebbe il rilascio.
Nonostante l'automazione, ARC non è una panacea. Gli sviluppatori incontrano diversi problemi tipici che richiedono la comprensione del meccanismo interno di gestione della memoria.
Le closures in Swift catturano le variabili esterne per riferimento strong. Se una closure viene assegnata a una proprietà di classe e cattura self — si verifica un retain cycle: la classe trattiene la closure, la closure trattiene self. La soluzione è una lista di cattura con weak o 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() { }
}
La lista di cattura [weak self] crea un riferimento weak a self all'interno della closure. Questo rompe il potenziale retain cycle. Guard let self garantisce che l'oggetto sia vivo prima di eseguire il codice. weak self è la pratica standard per le closures asincrone in Swift.
Sebbene retain/release siano operazioni leggere, nei loop intensivi gli incrementi/decrementi frequenti del contatore aggiungono overhead. In Swift 5.9+, il compilatore utilizza un'ottimizzazione che rimuove retain/release ridondanti se l'analizzatore dimostra che sono sicuri. Tuttavia, in Objective-C, retain/release possono ancora essere un collo di bottiglia in scenari ad alto carico con milioni di chiamate al secondo.
Autorelease Pool è un meccanismo di rilascio differito utilizzato in Objective-C e in alcuni scenari Swift. Gli oggetti vengono inseriti nel pool e ricevono release quando il pool viene svuotato. Nei loop con molti oggetti temporanei (ad esempio, analisi JSON), creare un autoreleasepool personalizzato riduce il consumo di picco di memoria.
Domande frequenti
Nella gestione manuale (MRR), lo sviluppatore chiamava esplicitamente retain, release e autorelease. ARC inserisce automaticamente queste chiamate in fase di compilazione, eliminando il rischio di double-free, perdite da release dimenticato ed errori di bilanciamento retain/release.
ARC gestisce solo oggetti Objective-C e classi Swift. Per strutture e puntatori C/C++, ARC non si applica — questi oggetti sono gestiti manualmente o tramite smart pointer C++ (shared_ptr, unique_ptr). Anche gli oggetti Core Foundation (CFString, CGColor) non sono coperti da ARC.
weak — quando l'oggetto può essere rilasciato prima dell'oggetto referenziante (delegati, closures asincrone). unowned — quando è garantito che l'oggetto viva almeno quanto il referenziante (genitore-figlio dove il figlio non può esistere senza il genitore). In caso di dubbio, scegli weak.
I tipi esistenziali (protocollo come tipo) in Swift incapsulano il valore in un contenitore speciale (contenitore esistenziale). Questo aumenta il numero di retain/release ai confini dei protocolli. In Swift 5.7+, i tipi di risultato opachi e i parametri some riducono l'overhead eliminando il contenitore.
Non esiste un'API diretta per leggere il retain count in Swift — è considerato un dettaglio implementativo. Per la diagnostica, usa Instruments (Allocations, Leaks) o il Debugger di Memoria in Xcode. Questi strumenti mostrano il numero di istanze di classe vive e le catene di ritenzione.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche