Blog

Schneller Selbstbezug im inneren Verschluss

Lammert Westerhoff

Aktualisiert Oktober 22, 2025
4 Minuten

Wir alle wissen ziemlich genau, wie man self sicher innerhalb einer Swift-Schließung verwendet. Aber haben Sie schon einmal versucht, self innerhalb einer Closure zu verwenden, die sich innerhalb einer anderen Closure befindet? Die Wahrscheinlichkeit ist groß, dass der Swift-Compiler abstürzt (Xcode 6.1.1), ohne dass Sie im Editor eine Fehlermeldung erhalten. Wie können Sie dieses Problem also lösen?

Der grundlegende Arbeitsabschluss

Bevor wir uns mit dem Problem und der Lösung beschäftigen, sehen wir uns zunächst ein funktionierendes Codebeispiel an, das nur eine einzige Schließung verwendet. Wir können einen einfachen Swift Playground erstellen, um ihn auszuführen und zu überprüfen, ob er funktioniert. [objc] Klasse Runner { var closures: [() -> ()] = [] func doSomethingAsync(completion: () -> ()) { Abschlüsse = [Abschluss] Abschluss() } } Klasse Playground { let Läufer = Läufer() func works() { runner.doSomethingAsync { [weak self] in self?.printMessage("Das funktioniert") ?? () } } func printMessage(message: String) { println(Nachricht) } deinit { println("Deinit") } } struct Tester { var Spielplatz: Spielplatz? = Spielplatz() } var tester: Tester? = Tester() tester?.playground?.works() tester?.playground = nil [/objc] Die Methode doSomethingAsync nimmt eine Closure ohne Argumente entgegen und hat den Rückgabetyp Void. Diese Methode tut eigentlich nichts, aber stellen Sie sich vor, sie würde Daten von einem Server laden und dann die Closure aufrufen, sobald sie fertig geladen ist. Sie erstellt jedoch einen starken Verweis auf die Closure, indem sie sie dem Closures-Array hinzufügt. Das bedeutet, dass wir innerhalb unserer Closure nur eine schwache Referenz auf self verwenden dürfen. Andernfalls würde der Runner eine starke Referenz auf die Playground-Instanz behalten und keine der beiden würde jemals freigegeben werden. Glücklicherweise ist alles in Ordnung und die Meldung "This works" wird in unserer Playground-Ausgabe ausgegeben. Außerdem wird die Meldung "Deinit" ausgegeben. Die Tester-Konstruktion wird verwendet, um sicherzustellen, dass der Playground die Zuweisung tatsächlich aufhebt.

Die missliche Lage

Lassen Sie uns die Dinge etwas komplexer gestalten. Wenn unser erster asynchroner Aufruf beendet ist und unsere Abschluss-Schließung aufruft, möchten wir noch etwas laden und müssen daher eine weitere Schließung innerhalb der äußeren Schließung erstellen. Wir fügen die folgende Methode zu unserer Playground-Klasse hinzu. Beachten Sie, dass die erste Closure nicht über [weak self] verfügt, da wir uns nur in der inneren Closure auf self beziehen. [objc] func doesntWork() { weak var weakRunner = runner runner.doSomethingAsync { // einige Dinge tun, für die wir self nicht brauchen weakRunner?.doSomethingAsync { [weak self] in self?.printMessage("Das funktioniert nicht") ?? () } ?? () } } [/objc] Schon das Hinzufügen führt zum Absturz des Compilers, ohne dass wir im Editor einen Fehler erhalten. Wir brauchen ihn nicht einmal auszuführen.

Bildschirmfoto 2014-12-19 um 10.30.59

Er gibt die folgende Meldung aus: Die Kommunikation mit dem Playground-Dienst wurde unerwartet unterbrochen. Der Playground-Dienst "com.apple.dt.Xcode.Playground" hat möglicherweise ein Absturzprotokoll erstellt. Und wenn Sie einen solchen Code in Ihrem normalen Projekt haben, gibt der Editor auch keine Fehlermeldung aus, aber der Build schlägt mit einem Swift Compiler Error fehl, ohne dass klar ist, was falsch ist: Command /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swiftc failed with exit code 1

Die Lösung

Wie können wir dieses Problem also umgehen? Eigentlich ganz einfach. Wir müssen einfach das [schwache Selbst] in den äußersten Verschluss verschieben. [objc] func doesWork() { weak var weakRunner = runner runner.doSomethingAsync { [weak self] in weakRunner?.doSomethingAsync { self?.printMessage("Das funktioniert jetzt wieder") ?? () } ?? () } } [/objc] Das bedeutet, dass es möglich ist, dass self in der äußeren Schließung nicht Null ist und in der inneren Schließung Null ist. Schreiben Sie also keinen Code wie diesen: [objc] runner.doSomethingAsync { [weak self] in if self != nil { self!.printMessage("Das ist gut, self ist nicht nil") weakRunner?.doSomethingAsync { self!.printMessage("Das ist nicht gut, self könnte jetzt nil sein") } ?? () } } [/objc] Es gibt noch ein weiteres Szenario, das Sie beachten sollten. Wenn Sie eine if let-Konstruktion verwenden, um self sicher zu entpacken, könnten Sie erneut eine starke Referenz auf self erzeugen. Das folgende Beispiel veranschaulicht dies und erzeugt einen Referenzzyklus, da unser Läufer aufgrund der inneren Schließung eine starke Referenz auf die Playground-Instanz erzeugt. [objc] runner.doSomethingAsync { [weak self] in if let this = self { weakRunner?.doSomethingAsync { this.printMessage("Erfasst eine starke Referenz auf self") } ?? () } } [/objc] Auch dies lässt sich leicht lösen, indem Sie wieder einen schwachen Verweis auf die Instanz einfügen, der jetzt this heißt. [objc] runner.doSomethingAsync { [weak self] in if let this = self { weakRunner?.doSomethingAsync { [weak this] in this?.printMessage("We're good again") ?? () } ?? () } } [/objc]

Fazit

Die meisten Leute, die mit Swift arbeiten, wissen, dass es immer noch eine ganze Reihe von Fehlern enthält. In diesem Fall sollte Xcode eine Fehlermeldung im Editor ausgeben. Wenn Ihr Editor sich nicht beschwert, aber Ihr Swift-Compiler versagt, suchen Sie nach Closures wie diesen und korrigieren Sie sie. Gehen Sie immer auf Nummer sicher und verwenden Sie [weak self]-Referenzen innerhalb von Closures.

Verfasst von

Lammert Westerhoff

Contact

Let’s discuss how we can support your journey.