Referenzzyklus in Swift

Mar 14 2023
Sogar einige erfahrene Entwickler machen solche Fehler
Die Speicherverwaltung ist für einen Entwickler normalerweise nicht trivial. Dank der automatischen Referenzzählung (ARC) wird die Speicherverwaltung für iOS-Entwickler vereinfacht.
Diagramm des Autors, Referenzzyklus zwischen zwei und mehr als zwei Objekten

Die Speicherverwaltung ist für einen Entwickler normalerweise nicht trivial. Dank der automatischen Referenzzählung (ARC) wird die Speicherverwaltung für iOS-Entwickler vereinfacht. Meistens funktioniert es einfach. Allerdings ist der Referenzzyklus sehr knifflig und ich habe viele erfahrene Entwickler gesehen, die solche Fehler gemacht haben.

In der offiziellen Dokumentation geht es hauptsächlich um den grundlegenden Referenzzyklus zwischen zwei Objekten A<->B. Was ich in diesem Artikel zeigen werde, ist der Referenzzyklus zwischen 3 oder 4 Objekten, der schwieriger zu erkennen ist.

Die App mit Memory Leak

Die einfache App zur Demo vom Autor

Die Demo ist ein sehr einfaches iOS-Projekt mit zwei Bildschirmen. Im ersten Bildschirm „InitialViewController“ kann der Benutzer auf die Schaltfläche klicken, um den zweiten Bildschirm „SampleViewController“ zu starten. Anschließend kann der Benutzer auf die Schaltfläche „Schließen“ klicken, um den zweiten Bildschirm zu schließen. Der zweite Bildschirm ist mit der VIP-Clean-Architektur codiert.

Während der Benutzer die App gerne nutzt, geht Speicher verloren. Auf dem folgenden Bildschirm der Xcode-Funktion „Debug Memory Graph“ können wir sehen, dass jeweils fünf Instanzen von „SampleViewController“, „SamplePresenter“, „SampleInteractor“ und „SampleRouter“ durchgesickert sind. Wenn zu viel Speicher verloren geht, wird die App vom System beendet, was für den App-Entwickler sehr peinlich ist.

Bildschirmdeckel vom Autor aus Xcode, Debug Memory Graph

Referenzzyklus innerhalb mehrerer Objekte

Schauen wir uns den folgenden Quellcode genau an, um herauszufinden, warum der Speicher verloren geht.

class SampleViewController: UIViewController, SampleViewControllerInterface {
    var interactor: SampleInteractorInterface?
    // omitted the rest for simplicity
}

class SampleInteractor: SampleInteractorInterface {
    var presenter: SamplePresenterInterface?
    // omitted the rest for simplicity
}

class SamplePresenter: SamplePresenterInterface {
    var viewController: SampleViewControllerInterface?
    var router: SampleRouterInterface?
    // omitted the rest for simplicity
}

class SampleRouter: SampleRouterInterface {
    var viewController: UIViewController?
    // omitted the rest for simplicity
}

Vom Autor erstelltes Diagramm, Referenzbeziehung zwischen Objekten

Es gibt tatsächlich zwei Referenzzyklen darin:

  1. Interactor->Presenter->ViewController->Interactor
  2. Interactor->Presenter->Router ->ViewController->Interactor

Um das Problem zu lösen, können wir eine schwache Referenzierung wie die folgende Änderung verwenden.

Bildschirmdeckel des Autors, wie man den Referenzzyklus für VIP korrigiert

Dann haben wir gemäß folgender Grafik keinen Referenzzyklus mehr für VIP.

Vom Autor erstelltes Diagramm, Referenzbeziehung zwischen Objekten nach der Korrektur

Referenzzyklus im Zusammenhang mit der Blockerfassung

Hui! Das fühlt sich großartig an, denn wir haben gerade zwei Referenzzyklen festgelegt, sind aber noch nicht fertig. Wenn Sie die App jetzt ausführen, werden Sie feststellen, dass immer noch Speicherlecks vorliegen. Die Ursache ist der folgende Teil des Codes.

class CustomButton: UIView {
    var buttonClickHander: (() -> Void)?
    // omitted the rest for simplicity
}

class SampleViewController: UIViewController, SampleViewControllerInterface {
   private lazy var customButton: CustomButton = {
        let button = CustomButton()
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()

    private func setUpUI() {
        view.addSubview(customButton)
        customButton.buttonClickHander = {
            self.interactor?.customButtonClicked()
        }
        // ... 
    }
}

Vom Autor erstelltes Diagramm, Referenzbeziehung zwischen Objekten und Block

Sobald wir wissen, wo das Problem liegt, ist die Behebung einfach, siehe unten.

Screenshot des Autors, wie man den Referenzzyklus für die Blockerfassung korrigiert

Zusammenfassung

Zusammenfassend haben wir zwei Arten von Referenzzyklen behandelt, die innerhalb mehrerer Objekte auftreten. Das Demoprojekt finden Sie hier . Die Korrektur wurde in einem Pull Request für Ihre einfache Überprüfung vorbereitet. Ich hoffe, dieser Artikel hilft Ihnen, den Referenzzyklus besser zu verstehen und ihn zu vermeiden.