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:
- Sterke referenties (Strong References): Verhogen de referentieteller van de instantie.
- 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
nilbij deinitialisatie van het object waarnaar ze verwijzen. - 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
nilkan 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.