Weak Reference (riferimento debole) è un riferimento a un oggetto che non aumenta il suo contatore di ritenzione in ARC. Secondo Apple Swift Language Guide, 2026, i riferimenti deboli vengono dichiarati con la parola chiave weak e sono sempre opzionali. Quando l'oggetto viene deallocato, tutti i riferimenti deboli ad esso vengono automaticamente impostati a nil, prevenendo puntatori pendenti e rendendo i riferimenti deboli un meccanismo sicuro per rompere i cicli di ritenzione.
Punti chiave
weak prima di var; tipo sempre opzionale (?)Weak Reference è un riferimento non proprietario a un oggetto in ARC (Automatic Reference Counting). A differenza di un riferimento forte, che aumenta il retain count dell'oggetto e ne garantisce la durata, un riferimento debole permette all'oggetto di essere deallocato anche se è ancora referenziato. Dopo la deallocazione, il riferimento debole viene automaticamente impostato a nil — questo si chiama zeroing weak.
Zeroing weak è una caratteristica chiave del runtime di Swift e Objective-C. Quando il contatore di riferimenti di un oggetto raggiunge zero e l'oggetto viene deallocato, il runtime scorre tutti i riferimenti deboli a questo oggetto (memorizzati in una tabella debole speciale) e li imposta a nil. Questo garantisce che l'accesso alla memoria liberata (use-after-free) sia impossibile tramite riferimenti deboli — qualsiasi lettura restituisce nil.
Secondo Apple WWDC 2012 Session 406, i riferimenti deboli zeroing hanno eliminato un'intera classe di bug di crash relativi ai puntatori pendenti (dangling pointers), che erano comuni nella gestione manuale della memoria (MRR). In MRR, i riferimenti deboli esistevano solo come __unsafe_unretained — non venivano azzerati e l'accesso a un oggetto deallocato provocava EXC_BAD_ACCESS.
Esaminiamo la sintassi per dichiarare riferimenti deboli in entrambi i linguaggi dell'ecosistema Apple. Nonostante il runtime condiviso, la sintassi differisce, ma la semantica è identica.
In Swift, i riferimenti deboli vengono dichiarati con la parola chiave weak prima di var. Il tipo deve essere sempre opzionale (Tipo?), poiché il riferimento può diventare nil in qualsiasi momento. Le costanti (let) non possono essere weak — solo le variabili.
class ViewController: UIViewController {
// weak properties: only var, only optional
weak var delegate: ViewControllerDelegate?
weak var parentView: UIView?
weak var completionHandler: ((Bool) -> Void)? // ⚠️ le chiusure non memorizzano weak
// ⬆️ Errore: weak può essere applicato solo a tipi class, non a closure
}
Importante: weak è applicabile solo a istanze di classe (tipi class), AnyObject e protocolli ereditati da AnyObject. Struct, enum e chiusure non possono essere weak — sono tipi valore e non partecipano ad ARC.
In Objective-C, le proprietà deboli vengono dichiarate usando l'attributo __weak o il modificatore weak nelle dichiarazioni di proprietà:
// Objective-C: weak property
@interface MyViewController : UIViewController
@property (weak, nonatomic) id<MyDelegate> delegate;
@end
// Variabile locale debole
__weak MyObject *weakRef = someStrongObject;
Anche il runtime Objective-C fornisce zeroing weak, ma blocca inoltre l'uso di weak con le struct C e alcuni oggetti Core Foundation. Per questi, si usa __unsafe_unretained — senza zeroing.
I riferimenti deboli non sono una soluzione universale, ma uno strumento per scenari specifici. Usare weak ovunque porta a complessità inutile e compromette la leggibilità. Esaminiamo gli scenari di uso corretti.
Delegati — lo scenario principale per weak. L'oggetto proprietario (es. UITableView) mantiene un riferimento forte a sé stesso, mentre il delegato (UIViewController) non dovrebbe possedere la tabella. L'SDK Apple garantisce che tutti i delegate e dataSource siano weak. Per i tuoi protocolli, usa sempre weak var delegate.
Quando un oggetto figlio deve referenziare il suo genitore (es. ChildViewController che accede a un coordinatore), usa un riferimento debole. Il genitore possiede il figlio (strong), il figlio osserva il genitore (weak) — il ciclo di ritenzione è eliminato.
Capture list [weak self] — il modo standard per evitare cicli di ritenzione nelle chiusure memorizzate come proprietà di classe. Se self può essere deallocato prima del completamento della chiusura, weak self è obbligatorio.
| Scenario | Weak | Strong |
|---|---|---|
| Delegate | ✅ Sempre weak | ❌ Retain cycle |
| Genitore → Figlio | ❌ Non necessario (genitore deve possedere) | ✅ Strong |
| Figlio → Genitore | ✅ Weak | ❌ Retain cycle |
| Callback asincrono | ✅ [weak self] | ❌ Rischio retain cycle |
| Accoppiamento forte (owned) | ❌ unowned | ✅ Strong |
Regola generale: se l'oggetto A possiede B (A → B strong), allora B → A dovrebbe essere weak o unowned. La direzione dei riferimenti forti deve essere sempre dal proprietario al subordinato.
Sia weak che unowned non aumentano il retain count, ma differiscono nel comportamento dopo la deallocazione dell'oggetto. La scelta tra loro è una questione di garanzie di durata.
Weak: diventa nil automaticamente, il tipo è sempre opzionale, richiede unwrap prima dell'uso. Sicuro — accedere a nil non causa crash.
Unowned: non diventa nil, il tipo è non opzionale. Se l'oggetto viene deallocato, un riferimento unowned diventa un puntatore pendente — accedervi causa un crash a runtime. Unowned presume che l'oggetto viva almeno quanto il lato referenziante.
Scegli weak se: l'oggetto può essere deallocato in qualsiasi momento (delegato dopo la chiusura dello schermo), non controlli la durata dell'oggetto, o sei incerto sulle garanzie. Weak è la scelta sicura universale.
Scegli unowned se: l'oggetto è garantito a non essere deallocato prima dell'oggetto referenziante (es. Cliente → CartaCredito, dove la carta non esiste senza il cliente). Unowned fornisce un'API non opzionale senza unwrap, che è più comoda nel codice.
class Order {
let id: Int
var items: [Item] = []
init(id: Int) { self.id = id }
// Relazione forte: Order possiede Item
func addItem(name: String) {
let item = Item(name: name, order: self)
items.append(item)
}
}
class Item {
let name: String
unowned let order: Order // ✅ unowned — Item non vive senza Order
init(name: String, order: Order) {
self.name = name
self.order = order
}
}
// Esempio con weak: delegato senza garanzia di durata
protocol NetworkServiceDelegate: AnyObject {
func didReceiveResponse(data: Data)
}
class NetworkService {
weak var delegate: NetworkServiceDelegate? // ✅ weak — il delegato può andarsene
}
Nell'esempio, Item usa unowned perché un elemento dell'ordine non può esistere senza l'ordine stesso — la garanzia di durata è ferrea. NetworkService usa weak perché il delegato (es. ViewController) può essere chiuso e deallocato in qualsiasi momento.
I riferimenti deboli sono uno strumento potente, ma hanno limitazioni che è importante comprendere per un uso corretto nello sviluppo iOS.
I riferimenti deboli sono più lenti di quelli forti: a ogni accesso, il runtime verifica se l'oggetto è stato deallocato (lookup nella tabella debole). Nella stragrande maggioranza degli scenari, la differenza è impercettibile, ma in cicli caldi con milioni di accessi, weak può diventare un collo di bottiglia. Per scenari ad alto carico, usa strong e riorganizza l'architettura.
Struct, enum, tuple — tipi valore che non partecipano ad ARC. Tentare di dichiarare un weak struct provoca un errore di compilazione. Per memorizzare un riferimento debole a un tipo valore, usa un wrapper in un tipo classe o una chiusura.
Zeroing weak è thread-safe: se un oggetto viene deallocato su un thread, il riferimento debole viene azzerato su tutti i thread atomicamente. Tuttavia, la finestra tra la lettura di un riferimento debole e il suo dereferenziamento può portare a una condizione di gara — l'oggetto viene deallocato tra l'ottenimento del riferimento debole e il suo utilizzo. Soluzione: cattura forte del riferimento debole in una variabile locale.
// Race condition con weak nel multithreading
func performAsync() {
weak var weakSelf = self
queue.async {
// ⚠️ weakSelf può essere nil tra controllo e utilizzo
if weakSelf != nil {
weakSelf!.doSomething() // CRASH se diventa nil
}
}
}
// ✅ Correzione: cattura forte durante l'uso
func performAsyncSafe() {
queue.async { [weak self] in
guard let strongSelf = self else { return }
strongSelf.doSomething() // strongSelf — riferimento locale forte
}
}
Nella versione sicura, weak self viene catturato, poi immediatamente unwrappato in una variabile locale forte strongSelf. Se self è ancora vivo, rimarrà vivo per la durata del blocco. Altrimenti, guard si attiva e il codice non viene eseguito. Questo idioma è il modello standard per le chiusure asincrone in Swift.
IBOutlet in Interface Builder dovrebbero essere weak perché la gerarchia delle viste mantiene già un riferimento forte alla subview. Duplicare un riferimento forte nel controller non crea un ciclo di ritenzione ma è ridondante. Un riferimento debole a un outlet è la raccomandazione di Apple, sebbene molti sviluppatori usino strong per semplicità di codice.
Domande frequenti
No, weak può puntare solo a un oggetto esistente o nil. Quando crei un nuovo oggetto, ottieni prima un riferimento forte (tramite un inizializzatore), e solo dopo puoi assegnare un riferimento debole. Un weak nil all'inizio è uno stato normale.
Weak si basa su ARC, che gestisce solo i tipi riferimento (classi). I tipi valore (struct, enum) vengono copiati all'assegnazione e non hanno retain count. Per relazioni deboli con tipi valore, usa chiusure o wrapper in una classe con una proprietà weak.
Ogni accesso a un riferimento debole esegue un lookup nella tabella del runtime. In un ciclo con milioni di iterazioni, può essere da 2 a 5 volte più lento di un riferimento forte. Per i percorsi caldi, copia weak in una variabile locale forte prima del ciclo.
Quando tutti i riferimenti forti all'oggetto vengono persi — alla fine dell'ambito, quando si riassegna una proprietà, o quando si chiude una schermata. In un ambiente multithread, questo può accadere tra due righe di codice. Controlla sempre i riferimenti deboli con guard let o if let.
Semanticamente identici: entrambi forniscono zeroing weak. Differenze: Swift richiede un tipo opzionale e var, Objective-C usa un modificatore di proprietà. Objective-C supporta anche __unsafe_unretained — un riferimento debole senza zeroing (rischio di puntatore pendente).
Riepilogo
weak var + tipo opzionale; solo tipi class e protocolli AnyObjectSvilupperemo 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