Strong Reference (riferimento forte) è un meccanismo standard di gestione della memoria in cui un oggetto rimane in memoria finché almeno un riferimento attivo punta ad esso. A differenza dei riferimenti deboli, un riferimento forte incrementa il contatore di riferimenti dell’oggetto e impedisce il suo rilascio automatico. Secondo la Apple Developer Documentation, ARC gestisce automaticamente il ciclo di vita degli oggetti in Swift e Objective-C. Comprendere il funzionamento dei riferimenti forti è fondamentale per prevenire perdite di memoria e dipendenze cicliche nelle applicazioni mobili.
Punti chiave
Strong Reference è un tipo di riferimento a un oggetto che impedisce la sua distruzione da parte del garbage collector o del sistema di gestione della memoria. Finché esiste almeno un riferimento forte all’oggetto, la sua memoria non viene liberata. Questo è il meccanismo di base su cui si basano ARC in Swift e Objective-C e la garbage collection in Java e Kotlin.
Il concetto di riferimento forte è fondamentale per tutti i linguaggi con gestione automatica della memoria. Nei sistemi con ARC, ogni riferimento forte incrementa il contatore di riferimenti dell’oggetto. Quando il contatore arriva a zero, l’oggetto viene immediatamente deallocato. In Java e Kotlin con garbage collection, un riferimento forte garantisce che l’oggetto sia raggiungibile e non verrà raccolto dal GC.
Secondo WWDC 2021, circa il 35% delle perdite di memoria nelle applicazioni iOS sono correlate all’uso errato di riferimenti forti e retain cycle. Nello sviluppo Android, le perdite attraverso riferimenti forti impliciti in closures e callback sono la seconda causa più comune di problemi di memoria dopo Context Leak.
Per lavorare efficacemente con la memoria, è necessario comprendere la differenza tra riferimenti strong, weak e unowned e scegliere il tipo di riferimento appropriato in base alla proprietà e al ciclo di vita degli oggetti.
Prima di ARC, gli sviluppatori chiamavano manualmente retain e release per ogni oggetto, causando numerosi errori. ARC, introdotto da Apple nel 2011 con LLVM 3.0, ha automatizzato questo processo analizzando il grafo di proprietà al momento della compilazione. Il compilatore stesso inserisce le chiamate retain, release e autorelease dove necessario.
Secondo Clang Static Analyzer, l’introduzione di ARC ha ridotto i bug relativi alla memoria nelle applicazioni iOS del 70%. Per gli sviluppatori, ciò significa che la gestione della memoria è diventata più sicura, ma allo stesso tempo è nata la necessità di capire come funzionano internamente i riferimenti forti — per evitare retain cycle.
In Kotlin e Java, il garbage collector svolge il ruolo di ARC, ma il principio del riferimento forte rimane lo stesso: GC Roots sono i punti di ingresso attraverso cui gli oggetti sono mantenuti da riferimenti forti. Finché un oggetto è raggiungibile attraverso una catena di riferimenti forti da un GC Root, non verrà raccolto.
ARC (Automatic Reference Counting) funziona contando i riferimenti per ogni oggetto nell’heap. Quando viene creato un nuovo riferimento forte a un oggetto, il contatore aumenta (retain). Quando il riferimento viene distrutto o sovrascritto, il contatore diminuisce (release). Quando il contatore raggiunge zero, l’oggetto viene immediatamente rimosso dalla memoria.
Consideriamo un esempio in Swift. Quando viene creata un’istanza di una classe, ARC alloca la memoria e imposta il retain count a 1. Ogni nuova assegnazione a un’altra variabile incrementa il contatore. Quando la variabile esce dall’ambito, il contatore diminuisce:
class ProfileViewController {
var nameLabel: String?
var avatarImage: UIImage?
func loadProfile() {
// retain count = 1 per una nuova istanza
let user = User(name: "Ivan")
// retain count = 2 dopo l’assegnazione di nameLabel
nameLabel = user.name
// uscita dal metodo — user esce dall’ambito, retain count = 1
}
}
In questo codice, ARC garantisce che l’oggetto User rimanga in memoria finché almeno un riferimento forte punta ad esso. Quando la funzione loadProfile termina, la variabile locale user viene distrutta, ma nameLabel mantiene ancora l’oggetto. La memoria verrà liberata solo quando nameLabel cesserà di esistere o verrà sovrascritta.
In Kotlin, un comportamento simile è fornito attraverso GC Roots. Finché esiste una catena tracciabile di riferimenti forti da una radice del garbage collector (ad esempio, un campo statico o un thread attivo), l’oggetto rimane in memoria. La differenza è che il GC non libera la memoria istantaneamente — avviene in modo asincrono dopo l’analisi di raggiungibilità.
In ARC, il rilascio avviene in modo sincrono quando il contatore raggiunge zero. In Swift e Objective-C, sai esattamente quando l’oggetto verrà rimosso. In Kotlin e Java, il momento del rilascio è imprevedibile, ma ciò è compensato da uno schema più flessibile per rilevare le dipendenze cicliche a livello del garbage collector.
Retain cycle (ciclo di ritenzione) — una situazione in cui due o più oggetti hanno riferimenti forti reciproci. Di conseguenza, il loro retain count non scende mai a zero e la memoria non viene mai liberata, anche dopo che gli oggetti non sono più necessari all’applicazione.
Un esempio classico: un view controller padre mantiene un oggetto figlio con un riferimento forte, e questo a sua volta mantiene il padre con un riferimento forte. Ciò è tipico in situazioni con delegati, closures ed espressioni lambda annidate. Secondo Instruments Leaks, i retain cycle rappresentano fino al 60% di tutte le perdite di memoria nelle applicazioni che utilizzano ARC.
class ParentViewController: UIViewController {
var child: ChildViewController?
func setupChild() {
child = ChildViewController()
// retain cycle: il padre mantiene il figlio, il figlio mantiene il padre tramite closure
child?.onEvent = {
self.handleEvent()
}
}
func handleEvent() {}
}
Il problema qui è che la closure onEvent cattura self (ParentViewController) con un riferimento forte, e ParentViewController stesso mantiene child con un riferimento forte. Entrambi gli oggetti non verranno mai liberati. La soluzione è utilizzare weak self nella closure per rompere il ciclo.
In Kotlin, cicli simili si verificano quando si utilizzano lambda che catturano oggetti esterni. Il garbage collector della JVM può rilevare questi cicli col tempo, ma solo se gli oggetti sono irraggiungibili dai GC Roots. Se il ciclo è legato a un thread attivo o a un contesto UI, la perdita persiste per tutta la vita dell’applicazione.
Comprendere la differenza tra i tipi di riferimento è la chiave per una gestione sicura della memoria. Strong Reference incrementa il retain count. Weak Reference non incrementa il retain count e diventa automaticamente nil quando l’oggetto viene deallocato. Unowned Reference non incrementa il retain count ma non viene azzerato — accedervi dopo la deallocazione provoca un crash.
| Tipo di riferimento | Retain count | Sicurezza | Quando usare |
|---|---|---|---|
| Strong | +1 | Sicuro (predefinito) | Proprietà dell’oggetto, relazione padre → figlio |
| Weak | Non cambia | Auto-azzeramento (sicuro) | Delegati, callbacks, riferimenti inversi |
| Unowned | Non cambia | Rischio di crash in caso di accesso tardivo | Quando l’oggetto vive garantitamente più a lungo del proprietario |
La scelta del tipo di riferimento è dettata dalla relazione di proprietà. Se l’oggetto B fa parte di A e non può esistere senza di esso — usa Strong. Se B può esistere indipendentemente e fa riferimento ad A per notifiche — usa Weak. Unowned è usato raramente — solo quando la durata dell’oggetto figlio non supera strettamente quella del genitore.
Apple Developer Documentation raccomanda: per impostazione predefinita, usa strong per tutte le relazioni di proprietà. Se devi evitare un retain cycle — determina quale riferimento dovrebbe essere debole. Di solito è il riferimento inverso nella gerarchia (figlio → padre). In Kotlin, un ruolo simile è svolto da WeakReference di java.lang.ref, utilizzato per cache e pattern observer.
Rilevare i retain cycle è il primo passo. Il secondo è eliminarli correttamente. Lo strumento principale per rompere i cicli di riferimenti forti è sostituire uno dei riferimenti con weak o unowned. Nei linguaggi con garbage collection, viene inoltre utilizzato WeakReference con controllo manuale di null prima di ogni accesso.
In Swift e Objective-C, la correzione più comune è aggiungere [weak self] nelle closure. Ciò garantisce che la closure non trattenga l’oggetto dopo la sua deallocazione. In Kotlin, wrapper WeakReference o pulizia esplicita dei riferimenti in onDestroy sono utilizzati per scopi simili.
class NetworkService {
func fetchData(completion: @escaping (Data?) -> Void) {
// cattura con weak self — retain cycle eliminato
URLSession.shared.dataTask(
with: URL(string: "https://api.example.com")!
) { [weak self] data, response, error in
guard let self else { return }
completion(data)
}.resume()
}
}
In questo esempio, [weak self] garantisce che NetworkService non sia trattenuto dalla closure dopo che non è più necessario. Se self viene deallocato prima del completamento della richiesta — guard let self else { return } esce dalla closure senza chiamare completion.
Per la diagnosi dei retain cycle, usa Instruments Leaks per iOS o Android Profiler + LeakCanary per Android. Questi strumenti mostrano il grafo esatto di ritenzione e indicano quale riferimento forte impedisce la deallocazione dell’oggetto. La profilazione regolare della memoria dovrebbe far parte del pipeline CI/CD di qualsiasi progetto mobile.
Swift e Kotlin utilizzano meccanismi di gestione della memoria fondamentalmente diversi, ma il concetto di riferimento forte esiste in entrambi. Swift utilizza ARC con rilascio sincrono al raggiungimento di retain count = 0. Kotlin utilizza un GC di tracciamento che pulisce in modo asincrono gli oggetti irraggiungibili.
| Parametro | Swift (ARC) | Kotlin (JVM GC) |
|---|---|---|
| Meccanismo | Conteggio dei riferimenti (retain count) | Tracciamento di raggiungibilità (GC Roots) |
| Rilascio | Sincrono (quando il contatore arriva a zero) | Asincrono (per ciclo GC) |
| Retain cycle | Non rilevato automaticamente | Il GC può rilevarlo, ma non immediatamente |
| Riferimento debole | weak (auto-azzeramento) | WeakReference (controllo manuale) |
La principale differenza pratica: in Swift, un retain cycle è una perdita garantita. In Kotlin, il GC può rompere il ciclo se gli oggetti sono irraggiungibili dalla radice, ma la durata degli oggetti persi rimane imprevedibile. Pertanto, in entrambi i linguaggi, la strategia migliore è evitare i cicli di riferimenti forti in fase di progettazione.
Per Swift, usa weak nei pattern di delegato e nelle closure. Per Kotlin, usa WeakReference o componenti Lifecycle-aware che cancellano automaticamente i riferimenti quando il proprietario viene distrutto. In entrambi gli approcci, l’obiettivo è lo stesso — eliminare i riferimenti forti dove creano una catena di ritenzione infrangibile.
Domande frequenti
Strong Reference incrementa il retain count dell’oggetto e impedisce il suo rilascio finché il riferimento esiste. Weak Reference non modifica il retain count e diventa automaticamente nil quando l’oggetto viene rimosso dalla memoria. I riferimenti forti sono usati per la proprietà, quelli deboli per connessioni inverse e delegati.
Retain cycle — un blocco reciproco in cui due oggetti si mantengono a vicenda con riferimenti forti. Il loro retain count non scende mai a zero, la memoria non viene liberata. Ciò porta a perdite di memoria: gli oggetti rimangono nell’heap per sempre, l’applicazione consuma sempre più risorse e alla fine si blocca con OutOfMemory.
Usa Instruments Leaks da Xcode — esegui la profilazione con il modello Leaks, esegui uno scenario nell’app e verifica gli indicatori di perdita. Per una diagnosi precisa, passa alla scheda Cycles & Roots — mostra il grafo dei riferimenti forti reciproci che formano un ciclo infrangibile.
Unowned va usato quando la durata dell’oggetto figlio garantitamente non supera quella del genitore — ad esempio, quando si lega un oggetto a un ambito strettamente definito. In caso di dubbio, usa Weak, poiché l’accesso a un riferimento unowned liberato provoca un crash dell’applicazione.
Indirettamente — sì. Ogni retain e release in ARC è un’operazione atomica con overhead. Con un gran numero di oggetti in cicli, ciò può influire sulle prestazioni. Tuttavia, il problema principale non è la velocità di ARC, ma le perdite di memoria dovute a un tipo di riferimento male scelto.
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