Hogyan működik az automatik memória kezelés (ARC) a Swiftben?
sobes.tech MI
Válasz az MI-től
Az ARC (Automatikus Referencia Számítás) Swiftben nyomon követi és kezeli az alkalmazásaid memóriahasználatát. Automatikusan felszabadítja a memória blokkokat, amelyeket az osztálypéldányok használnak, amikor már nincs rájuk szükség, ezáltal megelőzve a memória szivárgásokat.
Amikor egy új példányt hozol létre egy osztályból, az ARC lefoglal egy memória blokkot, hogy tárolja az adott példány adatait. Amikor az példány már nem szükséges, az ARC felszabadítja ezt a memóriát, hogy más célokra használható legyen.
Az ARC azzal működik, hogy számlálja az erős hivatkozások (strong references) számát minden osztálypéldányon. Az erős hivatkozás olyan hivatkozás, amely megtartja az példányt a memóriában. Amikor az erős hivatkozások száma nullára csökken, az ARC felszabadítja a vele összekapcsolt memóriát.
Swiftben három típusú hivatkozás létezik, amelyek befolyásolják az ARC-t:
- Erős hivatkozások (Strong References): növelik a hivatkozások számát az példányon.
- Gyenge hivatkozások (Weak References): nem növelik a hivatkozások számát. Használják, hogy elkerüljék az erős hivatkozási ciklusokat, amikor két osztálypéldány tart erős hivatkozásokat egymásra. A gyenge hivatkozás automatikusan
nil-re változik az objektum deinitializálásakor. - Bérelhető hivatkozások (Unowned References): szintén nem növelik a hivatkozások számát. Akkor használják, ha biztos vagy benne, hogy a hivatkozott példány mindig létezni fog, amíg a jelenlegi példány létezik. Nem lehet opcionális (
nil). A deinitializált objektumhoz való hozzáférés ilyen hivatkozással futási hibát eredményez.
Példa az erős hivatkozási ciklusra:
class Person {
let name: String
var apartment: Apartment?
init(name: String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
}
class Apartment {
let unit: String
var tenant: Person?
init(unit: String) {
self.unit = unit
print("Apartment \(unit) is being initialized")
}
deinit {
print("Apartment \(unit) is being deinitialized")
}
}
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
john!.apartment = unit4A
unit4A!.tenant = john
john = nil // Person nem deinitializálódik
unit4A = nil // Apartment nem deinitializálódik
A ciklus megoldásához használj gyenge vagy bérelt hivatkozásokat. Ebben az esetben, az tenant az Apartment-ben gyenge hivatkozás kell legyen:
class Person {
let name: String
var apartment: Apartment?
init(name: String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
}
class Apartment {
let unit: String
weak var tenant: Person? // Gyenge hivatkozás
init(unit: String) {
self.unit = unit
print("Apartment \(unit) is being initialized")
}
deinit {
print("Apartment \(unit) is being deinitialized")
}
}
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
john?.apartment = unit4A
unit4A?.tenant = john
john = nil // Person deinitializálódik
unit4A = nil // Apartment deinitializálódik
A választás a gyenge és a bérelt hivatkozás között az objektumok közötti kapcsolat típusától függ:
- Gyenge: Ha az egyik objektum
nil-ré válhat, mielőtt a másik deinitializálódik (opcionális érték). - Bérelhető: Ha az egyik objektum mindig létezik, amíg a másik (nem opcionális érték).
Az ARC a memória kezelését is irányítja, amelyet a lezárások (closures) használnak az osztálypéldányok fogására. Erős hivatkozási ciklusok alakulhatnak ki, amikor a lezárás fogja az osztálypéldányt, és ez az példány erős hivatkozással rendelkezik a lezárásra. Ennek elkerülésére használják a fogási listákat (capture lists) a lezárásokban.
class HTMLElement {
let name: String
let text: String?
lazy var asHTML: () -> String = {
// Nincs fogási lista, erős ciklus lehet
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) is being deinitialized")
}
}
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
paragraph = nil // HTMLElement nem deinitializálódik
A megoldás a fogási lista [weak self] használatával:
class AnHTMLElement {
let name: String
let text: String?
lazy var asHTML: () -> String = { [weak self] in // Gyenge self használata
guard let self = self else { return "" } // Ellenőrzés, hogy létezik-e 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) is being deinitialized")
}
}
var paragraph: AnHTMLElement? = AnHTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
paragraph = nil // AnHTMLElement deinitializálódik
A [unowned self] használható, ha biztos vagy benne, hogy az példány (self) nem válik nil-ré a lezárás végrehajtása előtt.
Az ARC jelentősen megkönnyíti a memória kezelését Swiftben a kézi kezeléshez képest, de megköveteli a hivatkozások típusának megértését a memória szivárgások elkerülése érdekében.