Sobes.tech
Middle

Как работи автоматичното управление на паметта (ARC) в Swift?

sobes.tech AI

Отговор от AI

ARC (Автоматично преброяване на референциите) в Swift следи и управлява използването на паметта на вашите приложения. Той автоматично освобождава паметта, използвана от екземплярите на класове, когато вече не са необходими, което предотвратява течове на памет.

При създаване на нов екземпляр на клас, ARC заделя блок памет за съхранение на информация за този екземпляр. Когато екземплярът вече не е нужен, ARC освобождава тази памет, за да може да бъде използвана за други цели.

ARC работи, като брои броя на силните препратки (strong references) към всеки екземпляр на клас. Силната препратка е препратка, която държи екземпляра в паметта. Когато броят на силните препратки към екземпляра стане нула, ARC освобождава свързаната с него памет.

В Swift съществуват три типа препратки, които влияят на ARC:

  1. Силни препратки (Strong References): увеличават броя на препратките към екземпляра.
  2. Слаби препратки (Weak References): не увеличават броя на препратките. Използват се, за да се избегнат цикли на силни препратки, когато два екземпляра на клас държат силни препратки един към друг. Слабата препратка автоматично става nil при деинициализация на обекта, към който сочи.
  3. Несвързани препратки (Unowned References): също не увеличават броя на препратките. Използват се, когато сте сигурни, че препратеният екземпляр винаги ще съществува, докато съществува текущият екземпляр. Не могат да бъдат опционални (nil). Опитът за достъп до деинициализиран обект чрез несвързана препратка ще доведе до грешка по време на изпълнение.

Пример за цикъл на силни препратки:

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

    init(name: String) {
        self.name = name
        print("\(name) се инициализира")
    }

    deinit {
        print("\(name) се деинициализира")
    }
}

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

    init(unit: String) {
        self.unit = unit
        print("Апартамент \(unit) се инициализира")
    }

    deinit {
        print("Апартамент \(unit) се деинициализира")
    }
}

var john: Person?
var unit4A: Apartment?

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

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

john = nil // Person не се деинициализира
unit4A = nil // Apartment не се деинициализира

За решаване на цикъла, използвайте слаби или несвързани препратки. В този случай, tenant в Apartment трябва да бъде слаба препратка:

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

    init(name: String) {
        self.name = name
        print("\(name) се инициализира")
    }

    deinit {
        print("\(name) се деинициализира")
    }
}

class Apartment {
    let unit: String
    weak var tenant: Person? // Използване на слаба препратка

    init(unit: String) {
        self.unit = unit
        print("Апартамент \(unit) се инициализира")
    }

    deinit {
        print("Апартамент \(unit) се деинициализира")
    }
}

var john: Person?
var unit4A: Apartment?

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

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

john = nil // Person се деинициализира
unit4A = nil // Apartment се деинициализира

Изборът между слаба и несвързана препратка зависи от връзката между обектите:

  • Слаба: Ако един от обектите може да стане nil преди другият да бъде деинициализиран (опционална стойност).
  • Несвързана: Ако един от обектите винаги съществува, докато съществува другият (неопционална стойност).

ARC също управлява паметта, използвана от затваряния (closures), които захващат екземпляри на класове. Цикли на силни препратки могат да възникнат, когато затваряне захване екземпляр на клас, а този екземпляр също има силна препратка към затварянето. За да се избегне това, се използват списъци за захващане (capture lists) в затварянията.

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

    lazy var asHTML: () -> String = {
        // Без списък за захващане, възможен силен цикъл
        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) се деинициализира")
    }
}

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

paragraph = nil // HTMLElement не се деинициализира

Решение с използване на списък за захващане [weak self]:

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

    lazy var asHTML: () -> String = { [weak self] in // Използване на weak self
        guard let self = self else { return "" } // Проверка за съществуване на 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) се деинициализира")
    }
}

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

paragraph = nil // AnHTMLElement се деинициализира

Използването на [unowned self] е възможно, ако сте сигурни, че екземплярът на класа (self) няма да стане nil преди изпълнението на затварянето.

ARC значително улеснява управлението на паметта в Swift в сравнение с ръчното управление, но изисква разбиране на типовете препратки, за да се избегнат течове на памет.