Cosa sono i cicli di ritenzione (retain cycles) in iOS?
sobes.tech AI
Risposta dell'AI
I cicli di ritenzione si verificano quando due o più oggetti hanno riferimenti forti tra loro, creando un "cerchio chiuso". Di conseguenza, il contatore di riferimenti di ogni oggetto non raggiunge mai zero e non può essere liberato da ARC (Automatic Reference Counting). Ciò porta a perdite di memoria, poiché gli oggetti rimangono in memoria anche dopo che non sono più utilizzati.
Esempio:
class Person {
let name: String
var apartment: Apartment?
init(name: String) {
self.name = name
print("\(name) viene inizializzato")
}
deinit {
print("\(name) viene de-inizializzato")
}
}
class Apartment {
let unit: String
var tenant: Person?
init(unit: String) {
self.unit = unit
print("Appartamento \(unit) viene inizializzato")
}
deinit {
print("Appartamento \(unit) viene de-inizializzato")
}
}
In questo esempio, se Person ha un riferimento forte a Apartment, e Apartment ha un riferimento forte a Person, al tentativo di liberare questi oggetti, non verranno eliminati, poiché i loro contatori di riferimenti non raggiungono zero.
Come risolvere il problema dei cicli di ritenzione:
Utilizzare riferimenti weak o unowned.
weak: Viene usato quando è possibile che l'oggetto collegato diventinildurante il ciclo di vita di un altro oggetto. Il riferimento diventa automaticamentenilquando l'oggetto a cui si riferisce viene rilasciato. È opzionale.unowned: Viene usato quando si sa che l'oggetto collegato ha la stessa o maggiore durata di vita rispetto all'oggetto che lo possiede. Non può esserenil.
Esempio corretto usando weak:
Modifichiamo il riferimento tenant nella classe Apartment in modo debole:
class Apartment {
let unit: String
weak var tenant: Person? // Usando weak
init(unit: String) {
self.unit = unit
print("Appartamento \(unit) viene inizializzato")
}
deinit {
print("Appartamento \(unit) viene de-inizializzato")
}
}
Ora, quando l'oggetto Person viene rilasciato, il riferimento tenant in Apartment diventa automaticamente nil, interrompendo il ciclo di ritenzione e consentendo anche a Apartment di essere rilasciato.
Tabella di confronto tra weak e unowned:
| Caratteristica | weak |
unowned |
|---|---|---|
| Tipo di riferimento | Opzionale | Non opzionale |
Può essere nil? |
Sì, quando l'oggetto viene rilasciato | No |
| Quando usarlo | Quando l'oggetto collegato può diventare nil |
Quando si sa che l'oggetto non sarà nil |
| Performance | Un po' più lento a causa del controllo nil |
Un po' più veloce |
Inoltre, i cicli di ritenzione possono verificarsi anche nelle chiusure (closures).
Quando una chiusura cattura proprietà dell'istanza o self, può creare un riferimento forte all'oggetto. Se anche questo oggetto ha un riferimento forte alla chiusura (ad esempio, tramite una proprietà di chiusura o delegato), si verifica un ciclo.
Come risolvere i cicli di ritenzione nelle chiusure:
Utilizzare lista di cattura (capture list).
class SomeClass {
var value = 0
func doSomethingDelayed() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in // Lista di cattura con weak self
guard let self = self else { return } // Controllo `nil` per weak self
self.value += 1
print("Il valore ora è \(self.value)")
}
}
}
In questo esempio, [weak self] crea una referenza debole a self all'interno della chiusura, prevenendo un ciclo di ritenzione.