Sobes.tech
Middle

Hoe werkt automatische geheugenbeheer (ARC) in Swift?

sobes.tech AI

Antwoord van AI

ARC (Automatische Referentie Telling) in Swift volgt en beheert het geheugengebruik van je applicaties. Het vrijwaart automatisch geheugen dat wordt gebruikt door klasse-instanties wanneer ze niet meer nodig zijn, wat geheugenlekken voorkomt.

Bij het maken van een nieuwe instantie van een klasse, wijst ARC een geheugenblok toe om informatie over die instantie op te slaan. Wanneer de instantie niet meer nodig is, vrijwaart ARC dat geheugen zodat het voor andere doeleinden kan worden gebruikt.

ARC werkt door het tellen van het aantal sterke referenties (strong references) naar elke klasse-instantie. Een sterke referentie is een verwijzing die de instantie in het geheugen houdt. Wanneer het aantal sterke referenties op nul komt, vrijwaart ARC het geheugen dat ermee is verbonden.

In Swift zijn er drie typen referenties die van invloed zijn op ARC:

  1. Sterke referenties (Strong References): Verhogen de referentieteller van de instantie.
  2. Zwakke referenties (Weak References): Verhogen de referentieteller niet. Gebruikt om cycli van sterke referenties te voorkomen wanneer twee klasse-instanties elkaar vasthouden met sterke referenties. Zwakke referenties worden automatisch nil bij deinitialisatie van het object waarnaar ze verwijzen.
  3. Onbezette referenties (Unowned References): Verhogen ook de referentieteller niet. Gebruikt wanneer je zeker weet dat de instantie waarnaar wordt verwezen altijd zal bestaan zolang de huidige instantie bestaat. Ze kunnen niet optioneel zijn (nil). Een poging om toegang te krijgen tot een gedeinitialiseerd object via een onbezette referentie leidt tot een runtime-fout.

Voorbeeld van een cyclische sterke referentie:

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

    init(name: String) {
        self.name = name
        print("\(name) wordt geïnitialiseerd")
    }

    deinit {
        print("\(name) wordt gedeinitialiseerd")
    }
}

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

    init(unit: String) {
        self.unit = unit
        print("Appartement \(unit) wordt geïnitialiseerd")
    }

    deinit {
        print("Appartement \(unit) wordt gedeinitialiseerd")
    }
}

var john: Person?
var unit4A: Apartment?

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

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

john = nil // Person wordt niet gedeinitialiseerd
unit4A = nil // Appartement wordt niet gedeinitialiseerd

Om dit probleem op te lossen, gebruik zwakke of onbezette referenties. In dit geval moet tenant in Apartment een zwakke referentie zijn:

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

    init(name: String) {
        self.name = name
        print("\(name) wordt geïnitialiseerd")
    }

    deinit {
        print("\(name) wordt gedeinitialiseerd")
    }
}

class Apartment {
    let unit: String
    weak var tenant: Person? // Gebruik van zwakke referentie

    init(unit: String) {
        self.unit = unit
        print("Appartement \(unit) wordt geïnitialiseerd")
    }

    deinit {
        print("Appartement \(unit) wordt gedeinitialiseerd")
    }
}

var john: Person?
var unit4A: Apartment?

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

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

john = nil // Person wordt gedeinitialiseerd
unit4A = nil // Appartement wordt gedeinitialiseerd

De keuze tussen zwakke en onbezette referenties hangt af van de relatie tussen de objecten:

  • Weak: Als een van de objecten nil kan worden voordat de andere wordt gedeinitialiseerd (optionele waarde).
  • Unowned: Als een van de objecten altijd bestaat zolang de andere bestaat (niet-optionele waarde).

ARC beheert ook het geheugen dat wordt gebruikt door closures die klasse-instanties vastleggen. Cycli van sterke referenties kunnen ontstaan wanneer een closure een instantie vastlegt en die instantie ook een sterke verwijzing naar de closure heeft. Om dit te voorkomen, worden capture lists in closures gebruikt.

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

    lazy var asHTML: () -> String = {
        // Geen capture list, mogelijk sterke cyclus
        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) wordt gedeinitialiseerd")
    }
}

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

paragraph = nil // HTMLElement wordt niet gedeinitialiseerd

Oplossing met [weak self]:

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

    lazy var asHTML: () -> String = { [weak self] in // Gebruik van weak self
        guard let self = self else { return "" } // Controleer of self bestaat
        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) wordt gedeinitialiseerd")
    }
}

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

paragraph = nil // AnHTMLElement wordt gedeinitialiseerd

UUsage [unowned self] is mogelijk als je zeker weet dat het instantie van de klasse (self) niet nil wordt voordat de closure wordt uitgevoerd.

ARC vereenvoudigt aanzienlijk het geheugenbeheer in Swift in vergelijking met handmatig beheer, maar vereist begrip van de typen referenties om geheugenlekken te voorkomen.