Sobes.tech
Middle

Jak działa mechanizm automatycznego zarządzania pamięcią (ARC) w języku Swift?

sobes.tech AI

Odpowiedź od AI

ARC (Automatyczne Liczenie Referencji) automatycznie śledzi i zarządza odwołaniami do instancji klas. Zwolni pamięć zajmowaną przez instancję, gdy nie ma już do niej silnych odwołań. ARC działa tylko z instancjami klas, nie z strukturami czy wyliczeniami, ponieważ są to typy wartości.

Trzy typy powiązań między instancjami:

  • Strong (Silne): Powiązanie, które zwiększa licznik odwołań do instancji. Domyślnie wszystkie odwołania są silne.
  • Weak (Słabe): Powiązanie, które nie zwiększa licznika odwołań. Używane do zapobiegania cyklom silnych odwołań. Odwołania typu weak są zawsze opcjonalne, ponieważ instancja może zostać zwolniona w dowolnym momencie.
  • Unowned (Bez właściciela): Powiązanie, które nie zwiększa licznika odwołań i jest używane, gdy jest gwarancja, że powiązana instancja będzie istnieć dłużej niż bieżąca. Nie jest obowiązkowe, ale próba dostępu do zwolnionej instancji spowoduje błąd wykonania.

Cykl silnych odwołań powstaje, gdy dwie lub więcej instancji klasy mają silne odwołania do siebie, uniemożliwiając ich zwolnienie przez ARC. Aby rozwiązać cykle, używa się odwołań weak lub unowned.

Przykład cyklu silnych odwołań:

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

    init(name: String) {
        self.name = name
    }

    deinit { // wywoływane przy zwolnieniu
        print("\(name) jest deinitializowany")
    }
}

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

    init(unit: String) {
        self.unit = unit
    }

    deinit { // wywoływane przy zwolnieniu
        print("Apartment \(unit) jest deinitializowany")
    }
}

var john: Person?
var unit4A: Apartment?

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

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

john = nil
unit4A = nil // Nic nie jest drukowane, ponieważ istnieje cykl silnych odwołań

Rozwiązanie cyklu z weak:

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

    init(name: String) {
        self.name = name
    }

    deinit {
        print("\(name) jest deinitializowany")
    }
}

class Apartment {
    let unit: String
    weak var tenant: Person? // Słabe odwołanie do Person

    init(unit: String) {
        self.unit = unit
    }

    deinit {
        print("Apartment \(unit) jest deinitializowany")
    }
}

var john: Person?
var unit4A: Apartment?

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

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

john = nil // Zwolnienie Person
unit4A = nil // Zwolnienie Apartment

Rozwiązanie z unowned:

class Customer {
    let name: String
    var card: CreditCard?

    init(name: String) {
        self.name = name
    }

    deinit {
        print("\(name) jest deinitializowany")
    }
}

class CreditCard {
    let number: UInt64
    unowned let customer: Customer // Rzecz bez właściciela

    init(number: UInt64, customer: Customer) {
        self.number = number
        self.customer = customer
    }

    deinit {
        print("Karta #\(number) jest deinitializowana")
    }
}

var john: Customer?

john = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)

john = nil // Zwolnienie Customer i CreditCard

ARC w zamknięciach (closures): Zamknięcia mogą również tworzyć cykle silnych odwołań, przechwytując instancje klas. Aby tego uniknąć, używa się list przechwytywania ([weak self] lub [unowned self]).

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

    lazy var asHTML: () -> String = { // lazy, aby móc używać self przed inicjalizacją
        [unowned self] in // Lista przechwytywania z "unowned 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) jest deinitializowany")
    }
}

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

paragraph = nil // Zwolnienie HTMLElement