Sobes.tech
Middle

Come funziona la gestione automatica della memoria (ARC) in Swift?

sobes.tech AI

Risposta dell'AI

L'ARC (Automatic Reference Counting) in Swift monitora e gestisce l'utilizzo della memoria delle tue applicazioni. Libera automaticamente la memoria utilizzata dagli istanti di classi quando non sono più necessari, prevenendo perdite di memoria.

Quando crei un nuovo istante di classe, l'ARC assegna un blocco di memoria per memorizzare le informazioni su quell'istanza. Quando non è più necessario, l'ARC libera quella memoria per poter essere riutilizzata.

L'ARC funziona contando il numero di riferimenti forti (strong references) a ogni istanza di classe. Un riferimento forte mantiene l'istanza in memoria. Quando il numero di riferimenti forti raggiunge zero, l'ARC libera la memoria associata.

Esistono tre tipi di riferimenti in Swift che influenzano l'ARC:

  1. Riferimenti Forti (Strong References): Aumentano il contatore di riferimenti dell'istanza.
  2. Riferimenti Deboli (Weak References): Non aumentano il contatore di riferimenti. Sono usati per evitare cicli di riferimenti forti, quando due istanze di classi si tengono reciprocamente con riferimenti forti. Il riferimento debole diventa automaticamente nil quando l'oggetto referenziato viene deallocato.
  3. Riferimenti Non Possedenti (Unowned References): Non aumentano neanche il contatore di riferimenti. Sono usati quando si è certi che l'istanza referenziata esisterà sempre finché l'istanza corrente esiste. Non possono essere opzionali (nil). Tentare di accedere a un oggetto deallocato tramite un riferimento non possedente causerà un errore in fase di esecuzione.

Esempio di ciclo di riferimenti forti:

class Person {
    let name: String
    var apartment: Apartment?

    init(name: String) {
        self.name = name
        print("\(name) viene inizializzato")
    }

    deinit {
        print("\(name) viene deallocato")
    }
}

class Apartment {
    let unit: String
    var tenant: Person?

    init(unit: String) {
        self.unit = unit
        print("Appartamento \(unit) viene inizializzato")
    }

    deinit {
        print("Appartamento \(unit) viene deallocato")
    }
}

var john: Person?
var unit4A: Apartment?

john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")

john!.apartment = unit4A
unit4A!.tenant = john

john = nil // Person non viene deallocato
unit4A = nil // Apartment non viene deallocato

Per risolvere il ciclo, usa riferimenti deboli o non possedenti. In questo caso, tenant in Apartment dovrebbe essere un riferimento debole:

class Person {
    let name: String
    var apartment: Apartment?

    init(name: String) {
        self.name = name
        print("\(name) viene inizializzato")
    }

    deinit {
        print("\(name) viene deallocato")
    }
}

class Apartment {
    let unit: String
    weak var tenant: Person? // Uso di riferimento debole

    init(unit: String) {
        self.unit = unit
        print("Appartamento \(unit) viene inizializzato")
    }

    deinit {
        print("Appartamento \(unit) viene deallocato")
    }
}

var john: Person?
var unit4A: Apartment?

john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")

john?.apartment = unit4A
unit4A?.tenant = john

john = nil // Person viene deallocato
unit4A = nil // Apartment viene deallocato

La scelta tra riferimento debole e non possedente dipende dalla relazione tra gli oggetti:

  • Debole (Weak): Se uno degli oggetti può diventare nil prima che l’altro venga deallocato (valore opzionale).
  • Non possedente (Unowned): Se uno degli oggetti esiste sempre finché l’altro esiste (valore non opzionale).

L'ARC gestisce anche la memoria usata da closure che catturano istanze di classi. Cicli di riferimenti forti possono verificarsi quando una closure cattura un'istanza di classe e questa ha una forte referenza sulla closure. Per evitarlo, si usano liste di cattura (capture lists) nelle closure.

class HTMLElement {
    let name: String
    let text: String?

    lazy var asHTML: () -> String = {
        // Nessuna lista di cattura, ciclo forte possibile
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name)>"
        }
    }

    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }

    deinit {
        print("\(name) viene deallocato")
    }
}

var paragraph: HTMLElement? = HTMLElement(name: "p", text: "ciao, mondo")
print(paragraph!.asHTML())

paragraph = nil // HTMLElement non viene deallocato

Soluzione usando lista di cattura [weak self]:

class AnHTMLElement {
    let name: String
    let text: String?

    lazy var asHTML: () -> String = { [weak self] in // Uso di weak self
        guard let self = self else { return "" } // Verifica dell’esistenza di self
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name)>"
        }
    }

    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }

    deinit {
        print("\(name) viene deallocato")
    }
}

var paragraph: AnHTMLElement? = AnHTMLElement(name: "p", text: "ciao, mondo")
print(paragraph!.asHTML())

paragraph = nil // AnHTMLElement viene deallocato

L’uso di [unowned self] è possibile se si è certi che l’istanza della classe (self) non diventerà nil prima dell’esecuzione della closure.

L'ARC semplifica molto la gestione della memoria in Swift rispetto alla gestione manuale, ma richiede la comprensione dei tipi di riferimenti per evitare perdite di memoria.