Sobes.tech
Middle

Τι είναι οι κύκλοι διατήρησης (retain cycles) στο iOS;

sobes.tech AI

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

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

Παράδειγμα:

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("Apartment \(unit) αρχικοποιείται")
    }

    deinit {
        print("Apartment \(unit) απελευθερώνεται")
    }
}

Σε αυτό το παράδειγμα, αν το Person έχει ισχυρή αναφορά στο Apartment και το Apartment έχει ισχυρή αναφορά στο Person, τότε κατά την προσπάθεια απελευθέρωσης αυτών των αντικειμένων, δεν θα διαγραφούν, καθώς οι μετρητές αναφορών τους δεν φτάνουν το μηδέν.

Πώς να λύσετε το πρόβλημα των κύκλων διατήρησης:

Χρησιμοποιήστε ασθενείς (weak) ή αδέσμευτες (unowned) αναφορές.

  • weak: Χρησιμοποιείται όταν είναι πιθανό το συσχετιζόμενο αντικείμενο να γίνει nil κατά τη διάρκεια του κύκλου ζωής ενός άλλου αντικειμένου. Η αναφορά γίνεται αυτόματα nil όταν το αντικείμενο στο οποίο αναφέρεται απελευθερώνεται. Είναι προαιρετικό.
  • unowned: Χρησιμοποιείται όταν είναι γνωστό ότι το συσχετιζόμενο αντικείμενο έχει τον ίδιο ή μεγαλύτερο κύκλο ζωής από το αντικείμενο που έχει την αναφορά. Δεν μπορεί να είναι nil.

Διορθωμένο παράδειγμα με χρήση weak:

Αλλάζουμε την αναφορά tenant στην κλάση Apartment σε ασθενή:

class Apartment {
    let unit: String
    weak var tenant: Person? // Χρησιμοποιούμε weak

    init(unit: String) {
        self.unit = unit
        print("Apartment \(unit) αρχικοποιείται")
    }

    deinit {
        print("Apartment \(unit)) απελευθερώνεται")
    }
}

Τώρα, όταν το αντικείμενο Person απελευθερώνεται, η αναφορά tenant στο Apartment γίνεται αυτόματα nil, διακόπτοντας τον κύκλο διατήρησης και επιτρέποντας στο Apartment επίσης να απελευθερωθεί.

Πίνακας σύγκρισης weak και unowned:

Χαρακτηριστικό weak unowned
Τύπος αναφοράς Προαιρετική Μη προαιρετική
Μπορεί να είναι nil? Ναι, όταν το αντικείμενο απελευθερώνεται Όχι
Πότε να χρησιμοποιείται Όταν το αντικείμενο μπορεί να γίνει nil Όταν το αντικείμενο δεν μπορεί να είναι nil
Απόδοση Ελαφρώς πιο αργό λόγω ελέγχου nil Ελαφρώς πιο γρήγορο

Επίσης, οι κύκλοι διατήρησης μπορούν να προκύψουν και σε κλείσιμο (closures).

Όταν ένα κλείσιμο καταγράφει ιδιότητες της κλάσης ή το self, μπορεί να δημιουργήσει ισχυρή αναφορά στο αντικείμενο. Αν αυτό το αντικείμενο έχει επίσης ισχυρή αναφορά στο κλείσιμο (π.χ. μέσω ιδιότητας-κλεισίματος ή delegate), δημιουργείται κύκλος διατήρησης.

Πώς να λύσετε το πρόβλημα των κύκλων διατήρησης σε κλεισίματα:

Χρησιμοποιήστε λίστα καταγραφής (capture list).

class SomeClass {
    var value = 0

    func doSomethingDelayed() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in // λίστα capture με weak self
            guard let self = self else { return } // έλεγχος `nil` για το weak self
            self.value += 1
            print("Τιμή τώρα \(self.value)")
        }
    }
}

Σε αυτό το παράδειγμα, το [weak self] δημιουργεί ασθενή αναφορά στο self μέσα στο κλείσιμο, αποτρέποντας τον κύκλο διατήρησης.