Sobes.tech
Middle

Kako funkcioniše automatsko upravljanje memorijom (ARC) u Swift-u?

sobes.tech АИ

Одговор од АИ

ARC (Automatsko brojanje referenci) u Swiftu prati i upravlja korišćenjem memorije vaših aplikacija. Automatski oslobađa memoriju koju koriste instance klasa kada više nisu potrebne, čime sprečava curenje memorije.

Prilikom kreiranja nove instance klase, ARC dodeljuje blok memorije za čuvanje informacija o toj instanci. Kada instanca više nije potrebna, ARC oslobađa tu memoriju, tako da može biti korišćena za druge svrhe.

ARC funkcioniše tako što broji broj jakih referenci (strong references) na svaku instancu klase. Jaka referenca je ona koja drži instancu u memoriji. Kada broj jakih referenci na instancu padne na nulu, ARC oslobađa povezanu memoriju.

U Swiftu postoje tri tipa referenci koje utiču na ARC:

  1. Jake reference (Strong References): povećavaju brojač referenci na instancu.
  2. Slabe reference (Weak References): ne povećavaju brojač referenci. Koriste se da bi se izbegli ciklusi jakih referenci, kada dve instance klase drže jake reference jedna na drugu. Slaba referenca automatski postaje nil pri deinitializaciji objekta na koji ukazuje.
  3. Nepovezane reference (Unowned References): takođe ne povećavaju brojač referenci. Koriste se kada ste sigurni da će referencirani objekat uvek postojati dok postoji trenutni objekat. Ne mogu biti opcionalne (nil). Pokušaj pristupa deinitializovanom objektu putem nepovezane reference izazvaće grešku u izvršenju.

Primer ciklusa jakih referenci:

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

    init(name: String) {
        self.name = name
        print("\(name) se inicijalizuje")
    }

    deinit {
        print("\(name) se deinitializuje")
    }
}

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

    init(unit: String) {
        self.unit = unit
        print("Stan \(unit) se inicijalizuje")
    }

    deinit {
        print("Stan \(unit) se deinitializuje")
    }
}

var john: Person?
var unit4A: Apartment?

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

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

john = nil // Person se ne deinitializuje
unit4A = nil // Apartment se ne deinitializuje

Da bi se rešio ciklus, koristite slabe ili nepovezane reference. U ovom slučaju, tenant u Apartment treba biti slaba referenca:

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

    init(name: String) {
        self.name = name
        print("\(name) se inicijalizuje")
    }

    deinit {
        print("\(name) se deinitializuje")
    }
}

class Apartment {
    let unit: String
    weak var tenant: Person? // Korišćenje slabe reference

    init(unit: String) {
        self.unit = unit
        print("Stan \(unit) se inicijalizuje")
    }

    deinit {
        print("Stan \(unit) se deinitializuje")
    }
}

var john: Person?
var unit4A: Apartment?

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

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

john = nil // Person se deinitializuje
unit4A = nil // Apartment se deinitializuje

Izbor između slabe i nepovezane reference zavisi od odnosa između objekata:

  • Slaba: Ako jedan od objekata može postati nil pre nego što se drugi deinitializuje (opciona vrednost).
  • Nepovezana: Ako jedan od objekata uvek postoji dok postoji drugi (neopciona vrednost).

ARC takođe upravlja memorijom koju koriste zatvaranja (closures) koja hvata instance klasa. Ciklus jakih referenci može nastati kada zatvaranje hvata instancu klase, a ta klasa takođe ima jaku referencu na zatvaranje. Za sprečavanje toga koriste se liste hvatanja (capture lists) u zatvaranjima.

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

    lazy var asHTML: () -> String = {
        // Bez liste hvatanja, mogući jak ciklus hvatanja
        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) se deinitializuje")
    }
}

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

paragraph = nil // HTMLElement se ne deinitializuje

Rešenje korišćenjem liste hvatanja [weak self]:

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

    lazy var asHTML: () -> String = { [weak self] in // Korišćenje weak self
        guard let self = self else { return "" } // Provera postojanja 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) se deinitializuje")
    }
}

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

paragraph = nil // AnHTMLElement se deinitializuje

Korišćenje [unowned self] je moguće ako ste sigurni da instanca klase (self) neće postati nil pre izvršenja zatvaranja.

ARC znatno pojednostavljuje upravljanje memorijom u Swift-u u poređenju sa ručnim upravljanjem, ali zahteva razumevanje tipova referenci kako bi se sprečili curenje memorije.