Sobes.tech
Middle

Kaip veikia automatinis atminties valdymas (ARC) Swift?

sobes.tech AI

Atsakymas iš AI

ARC (Automatinė nuorodų skaičiavimo sistema) Swift'e stebi ir valdo jūsų programų atminties naudojimą. Ji automatiškai atlaisvina klasių pavyzdžių naudojamą atmintį, kai jie nebėra reikalingi, taip išvengdama atminties nutekėjimo:

Kuriant naują klasės pavyzdį, ARC paskirsto atminties bloką šiai informacijai saugoti. Kai pavyzdys nebėra reikalingas, ARC atlaisvina tą atmintį, kad ją galėtų naudoti kitiems tikslams:

ARC veikia skaičiuodamas kiekvieno klasės pavyzdžio stipriųjų nuorodų (strong references) skaičių. Stipri nuoroda — tai nuoroda, kuri laiko pavyzdį atmintyje. Kai stipriųjų nuorodų skaičius pasiekia nulį, ARC atlaisvina tą atmintį:

Swift'e yra trys nuorodų tipai, kurie veikia ARC:

  1. Stipriosios nuorodos (Strong References): didina nuorodų skaičių pavyzdyje.
  2. Sunkiosios nuorodos (Weak References): nepadidina nuorodų skaičiaus. Naudojamos, kad išvengtų stipriųjų nuorodų ciklų, kai du klasės pavyzdžiai laiko stipriąsias nuorodas vienas į kitą. Sunkioji nuoroda automatiškai tampa nil, kai objektas, į kurį ji nuoroda, yra dėsniuojamas:
  3. Nepasiekiamos nuorodos (Unowned References): taip pat nepadidina nuorodų skaičiaus. Naudojamos, kai esate tikri, kad nuoroda į objektą visada egzistuos, kol egzistuos dabartinis pavyzdys. Negali būti pasirinktinė (nil). Bandymas pasiekti dėsniuojamą objektą per nepastebimą nuorodą sukels klaidą:

Pavyzdys stipriųjų nuorodų ciklo:

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

    init(name: String) {
        self.name = name
        print("\(name) yra inicijuotas")
    }

    deinit {
        print("\(name) yra dėsniuojamas")
    }
}

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

    init(unit: String) {
        self.unit = unit
        print("Butas \(unit) yra inicijuotas")
    }

    deinit {
        print("Butas \(unit) yra dėsniuojamas")
    }
}

var john: Person?
var unit4A: Apartment?

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

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

john = nil // Person nėra dėsniuojamas
unit4A = nil // Apartment nėra dėsniuojamas

Sprendžiant problemą, naudokite silpną arba nepastebimą nuorodą. Šiuo atveju, tenant Apartment turi būti silpna nuoroda:

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

    init(name: String) {
        self.name = name
        print("\(name) yra inicijuotas")
    }

    deinit {
        print("\(name) yra dėsniuojamas")
    }
}

class Apartment {
    let unit: String
    weak var tenant: Person? // Silpna nuoroda

    init(unit: String) {
        self.unit = unit
        print("Butas \(unit) yra inicijuotas")
    }

    deinit {
        print("Butas \(unit) yra dėsniuojamas")
    }
}

var john: Person?
var unit4A: Apartment?

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

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

john = nil // Person yra dėsniuojamas
unit4A = nil // Apartment yra dėsniuojamas

Pasirinkimas tarp silpnos ir nepastebimos nuorodos priklauso nuo objektų santykio:

  • Silpna (Weak): Jei vienas iš objektų gali tapti nil iki kito dėsniuojant (pasirinktinė reikšmė).
  • Nepastebima (Unowned): Jei vienas iš objektų visada egzistuos, kol egzistuos kitas (nepasirinktinė reikšmė).

ARC taip pat valdo atmintį, naudojamą uždarymuose (closures), kurie įtraukia klasių pavyzdžius. Stiprių ciklų gali atsirasti, kai uždarymas įtraukia klasės pavyzdį, ir šis pavyzdys taip pat turi stiprią nuorodą į uždarymą. Norint to išvengti, naudojami įrašų sąrašai (capture lists) uždarymuose:

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

    lazy var asHTML: () -> String = {
        // Be įrašų sąrašo, gali būti stiprus ciklas
        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) yra dėsniuojamas")
    }
}

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

paragraph = nil // HTMLElement nėra dėsniuojamas

Sprendimas naudojant įrašų sąrašą [weak self]:

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

    lazy var asHTML: () -> String = { [weak self] in // Naudojama silpna nuoroda
        guard let self = self else { return "" } // Patikrinimas, ar `self` egzistuoja
        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) yra dėsniuojamas")
    }
}

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

paragraph = nil // AnHTMLElement yra dėsniuojamas

Naudojimas [unowned self] yra įmanomas, jei esate tikri, kad self nebus nil iki uždarymo vykdymo.

ARC žymiai supaprastino atminties valdymą Swift, palyginti su rankiniu valdymu, tačiau reikalauja suprasti nuorodų tipus, kad būtų išvengta atminties nutekėjimo.