Sobes.tech
Middle

Πώς λειτουργεί η αυτόματη διαχείριση μνήμης (ARC) στο Swift;

sobes.tech AI

Απάντηση από AI

Το ARC (Automatic Reference Counting) στην Swift παρακολουθεί και διαχειρίζεται τη χρήση μνήμης από τις εφαρμογές σας. Απελευθερώνει αυτόματα τη μνήμη που χρησιμοποιείται από τις περιπτώσεις κλάσεων όταν δεν είναι πλέον απαραίτητες, αποτρέποντας διαρροές μνήμης.

Κατά τη δημιουργία μιας νέας περίπτωσης κλάσης, το ARC εκχωρεί ένα μπλοκ μνήμης για την αποθήκευση πληροφοριών σχετικά με αυτήν την περίπτωση. Όταν η περίπτωση δεν είναι πλέον απαραίτητη, το ARC ελευθερώνει αυτήν τη μνήμη ώστε να μπορεί να χρησιμοποιηθεί για άλλους σκοπούς.

Το ARC λειτουργεί μετρώντας τον αριθμό των ισχυρών αναφορών (strong references) σε κάθε περίπτωση κλάσης. Μια ισχυρή αναφορά είναι μια αναφορά που διατηρεί την περίπτωση στη μνήμη. Όταν ο αριθμός των ισχυρών αναφορών φτάσει στο μηδέν, το ARC απελευθερώνει τη σχετική μνήμη.

Στην Swift υπάρχουν τρεις τύποι αναφορών που επηρεάζουν το ARC:

  1. Ισχυρές αναφορές (Strong References): Αυξάνουν τον μετρητή αναφορών της περίπτωσης.
  2. Αδύναμες αναφορές (Weak References): Δεν αυξάνουν τον μετρητή αναφορών. Χρησιμοποιούνται για την αποφυγή κυκλικών ισχυρών αναφορών όταν δύο περιπτώσεις κλάσεων κρατούν η μία την άλλη με ισχυρές αναφορές. Οι αδύναμες αναφορές γίνονται αυτόματα nil κατά την deinitialization του αντικειμένου στο οποίο αναφέρονται.
  3. Ανεξάρτητες αναφορές (Unowned References): Επίσης, δεν αυξάνουν τον μετρητή αναφορών. Χρησιμοποιούνται όταν είστε βέβαιοι ότι η περίπτωση στην οποία αναφέρονται θα υπάρχει πάντα όσο υπάρχει η τρέχουσα περίπτωση. Δεν μπορούν να είναι προαιρετικές (nil). Η πρόσβαση σε ένα deinitialized αντικείμενο μέσω μιας ανεξάρτητης αναφοράς θα οδηγήσει σε σφάλμα εκτέλεσης.

Παράδειγμα κυκλικής ισχυρής αναφοράς:

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 // Το Διαμέρισμα δεν τερματίζεται

Για την επίλυση του προβλήματος του κυκλικού αναφορών, χρησιμοποιήστε αδύναμες ή ανεξάρτητες αναφορές. Στην περίπτωση αυτή, το 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 // Το Διαμέρισμα τερματίζεται

Η επιλογή μεταξύ αδύναμων και ανεξάρτητων αναφορών εξαρτάται από τη σχέση μεταξύ των αντικειμένων:

  • Weak: Αν ένα από τα αντικείμενα μπορεί να γίνει nil πριν από την deinitialization του άλλου (προαιρετική τιμή).
  • Unowned: Αν ένα από τα αντικείμενα υπάρχει πάντα όσο το άλλο (μη προαιρετική τιμή).

Το ARC διαχειρίζεται επίσης τη μνήμη που χρησιμοποιείται από closures που καταγράφουν περιπτώσεις κλάσεων. Κυκλικές ισχυρές αναφορές μπορεί να προκύψουν όταν ένα closure καταγράφει μια περίπτωση κλάσης και αυτή η περίπτωση έχει επίσης ισχυρή αναφορά στο closure. Για την αποτροπή αυτού, χρησιμοποιούνται λίστες καταγραφής (capture lists) στα closures.

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: "salam, dünya")
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: "salam, dünya")
print(paragraph!.asHTML())

paragraph = nil // AnHTMLElement τερματίζεται

Η χρήση [unowned self] είναι δυνατή αν είστε βέβαιοι ότι το αντικείμενο (self) δεν θα γίνει nil πριν την εκτέλεση του closure.

Το ARC απλοποιεί σημαντικά τη διαχείριση μνήμης στη Swift σε σύγκριση με την χειροκίνητη διαχείριση, αλλά απαιτεί κατανόηση των τύπων αναφορών για την αποφυγή διαρροών μνήμης.