510 Stimmen

Sollen wir innerhalb einer Closure in Swift immer [unowned self] verwenden?

Bei der WWDC 2014 Session 403 Intermediate Swift und Transkript gab es die folgende Folie

Bildbeschreibung hier eingeben

Der Sprecher sagte in diesem Fall, wenn wir dort nicht [unowned self] verwenden, wird es zu einem Memory-Leak kommen. Bedeutet das, dass wir immer [unowned self] in Closures verwenden sollten?

In der Zeile 64 von ViewController.swift der Swift Weather App verwende ich kein [unowned self]. Aber ich aktualisiere die UI, indem ich einige @IBOutlets wie self.temperature und self.loadingIndicator verwende. Es könnte in Ordnung sein, weil alle von mir definierten @IBOutlets weak sind. Aber aus Sicherheitsgründen sollten wir immer [unowned self] verwenden?

class TempNotifier {
  var onChange: (Int) -> Void = {_ in }
  var currentTemp = 72
  init() {
    onChange = { [unowned self] temp in
      self.currentTemp = temp
    }
  }
}

928voto

drewag Punkte 91541

Nein, es gibt definitiv Zeiten, in denen du [unowned self] nicht verwenden möchtest. Manchmal möchtest du, dass die Closure self erfasst, um sicherzustellen, dass es noch vorhanden ist, wenn die Closure aufgerufen wird.

Beispiel: Durchführung einer asynchronen Netzwerkanfrage

Wenn du eine asynchrone Netzwerkanfrage durchführst, möchtest du, dass die Closure self behält, wenn die Anfrage beendet ist. Dieses Objekt könnte sonst deallokiert worden sein, aber du möchtest immer noch in der Lage sein, die Beendigung der Anfrage zu behandeln.

Wann unowned self oder weak self verwenden

Die einzige Zeit, in der du wirklich [unowned self] oder [weak self] verwenden möchtest, ist, wenn du einen strong reference cycle erstellen würdest. Ein starker Verweiszirkel entsteht, wenn es eine Besitzschleife gibt, in der Objekte sich gegenseitig besitzen (vielleicht über einen Dritten) und daher niemals deallokiert werden, weil sie beide sicherstellen, dass der andere bestehen bleibt.

In dem spezifischen Fall einer Closure musst du nur erkennen, dass jede Variable, auf die darin referenziert wird, von der Closure "besessen" wird. Solange die Closure vorhanden ist, sind diese Objekte garantiert vorhanden. Der einzige Weg, um diesen Besitz zu stoppen, ist [unowned self] oder [weak self] zu verwenden. Wenn eine Klasse also eine Closure besitzt und diese Closure einen starken Verweis auf diese Klasse erfasst, hast du einen starken Verweiszirkel zwischen der Closure und der Klasse. Das gilt auch, wenn die Klasse etwas besitzt, das die Closure besitzt.

Speziell im Beispiel aus dem Video

In dem Beispiel auf der Folie besitzt TempNotifier die Closure durch die onChange-Instanzvariablе. Wenn sie self nicht als unowned deklariert hätten, würde die Closure auch self besitzen und einen starken Verweiszirkel erstellen.

Unterschied zwischen unowned und weak

Der Unterschied zwischen unowned und weak ist, dass weak als Optional deklariert ist, während unowned es nicht ist. Indem du es als weak deklarierst, kannst du damit umgehen, dass es innerhalb der Closure irgendwann nil sein könnte. Wenn du versuchst, auf eine unowned-Variable zuzugreifen, die zufällig nil ist, wird das ganze Programm abstürzen. Verwende also nur unowned, wenn du sicher bist, dass die Variable immer vorhanden sein wird, solange die Closure vorhanden ist

218voto

uraimo Punkte 18505

Aktualisierung 11/2016

Ich habe einen Artikel zu diesem Thema verfasst, der diese Antwort erweitert (SIL analysiert, um zu verstehen, was ARC macht), schau ihn dir hier an hier.

Ursprüngliche Antwort

Die vorherigen Antworten geben keine klaren Regeln darüber, wann man das eine anstelle des anderen verwenden sollte und warum, also lasst mich ein paar Dinge hinzufügen.

Die Diskussion über unowned oder weak kommt auf die Frage nach der Lebensdauer der Variablen und des Closures, das darauf verweist, hinunter.

swift weak vs unowned

Szenarien

Es gibt zwei mögliche Szenarien:

  1. Das Closure hat dieselbe Lebensdauer wie die Variable, sodass das Closure nur erreichbar ist, solange die Variable erreichbar ist. Die Variable und das Closure haben dieselbe Lebensdauer. In diesem Fall solltest du die Referenz als unowned deklarieren. Ein häufiges Beispiel ist das [unowned self], das in vielen Beispielen von kleinen Closures verwendet wird, die etwas im Kontext ihrer übergeordneten Instanz tun und die nicht an anderer Stelle referenziert werden und ihre übergeordneten Instanzen nicht überleben.

  2. Die Lebensdauer des Closures ist unabhängig von der der Variable, das Closure könnte immer noch referenziert werden, wenn die Variable nicht mehr erreichbar ist. In diesem Fall solltest du die Referenz als weak deklarieren und überprüfen, ob sie nicht nil ist, bevor du sie verwendest (kein force unwrap). Ein häufiges Beispiel dafür ist das [weak delegate], das in einigen Beispielen von Closures zu sehen ist, die auf ein völlig nicht verwandtes (lebensdauerbezogen) Delegationsobjekt verweisen.

Tatsächliche Verwendung

Also, welches sollte/solltest du tatsächlich die meiste Zeit verwenden?

Zitat von Joe Groff auf Twitter:

Unowned ist schneller und ermöglicht Unveränderlichkeit und Nicht-Optionalität.

Wenn du weak nicht benötigst, benutze es nicht.

Mehr über das Innenleben von unowned findest du hier.

* Normalerweise auch als unowned(safe) bezeichnet, um anzuzeigen, dass zur Laufzeit Überprüfungen (die zu einem Absturz bei ungültigen Referenzen führen) durchgeführt werden, bevor auf die unowned-Referenz zugegriffen wird.

153voto

possen Punkte 7638

Ich dachte, ich würde einige konkrete Beispiele speziell für einen Ansichtscontroller hinzufügen. Viele der Erklärungen, nicht nur hier auf Stack Overflow, sind wirklich gut, aber ich arbeite besser mit realen Beispielen (@drewag hatte einen guten Start damit):

  • Wenn Sie einen Closure haben, um eine Antwort von einem Netzwerkanforderungen zu verarbeiten, verwenden Sie weak, weil sie lange leben. Der Ansichtscontroller könnte geschlossen werden, bevor die Anfrage abgeschlossen ist, sodass self nicht mehr auf ein gültiges Objekt zeigt, wenn die Closure aufgerufen wird.
  • Wenn Sie eine Closure haben, die ein Ereignis auf einer Schaltfläche behandelt. Diese kann unowned sein, weil sobald der Ansichtscontroller verschwindet, verschwinden die Schaltfläche und alle anderen Elemente, auf die sie möglicherweise von self aus verweist, zur gleichen Zeit. Der Closure-Block verschwindet auch zur gleichen Zeit.

    class MyViewController: UIViewController {
          @IBOutlet weak var myButton: UIButton!
          let networkManager = NetworkManager()
          let buttonPressClosure: () -> Void // Closure muss in dieser Klasse gehalten werden.
    
          override func viewDidLoad() {
              // hier unowned verwenden
              buttonPressClosure = { [unowned self] in
                  self.changeDisplayViewMode() // passiert nicht mehr, nachdem der vc geschlossen ist.
              }
              // hier weak verwenden
              networkManager.fetch(query: query) { [weak self] (results, error) in
                  self?.updateUI() // kann jederzeit nach Schließen des VC aufgerufen werden
              }
          }
          @IBAction func buttonPress(self: Any) {
             buttonPressClosure()
          }
    
          // Rest der Klasse unten.
     }

72voto

TenaciousJay Punkte 6670

Wenn self in der Closure nil sein könnte, verwende [weak self].

Wenn self in der Closure niemals nil sein wird, verwende [unowned self].

Die Apple Swift-Dokumentation enthält einen großartigen Abschnitt mit Abbildungen, die den Unterschied zwischen der Verwendung von strong, weak und unowned in Closures erklären:

https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html

55voto

Valentin Shergin Punkte 6896

Hier sind brillante Zitate aus den Apple Developer Foren mit köstlichen Details:

unowned vs unowned(safe) vs unowned(unsafe)

unowned(safe) ist eine Nicht-Besitz-Referenz, die beim Zugriff voraussetzt, dass das Objekt noch lebt. Es ist so etwas wie eine schwache optionale Referenz, die jedes Mal mit x! implizit entpackt wird, wenn darauf zugegriffen wird. unowned(unsafe) ist ähnlich wie __unsafe_unretained in ARC - es ist eine Nicht-Besitz-Referenz, aber es gibt keine Laufzeitprüfung, ob das Objekt beim Zugriff noch lebt, sodass hängende Referenzen in den Garbage-Speicher greifen werden. unowned ist derzeit immer ein Synonym für unowned(safe), aber die Absicht ist, dass es in -Ofast Optimierungen zu unowned(unsafe) wird, wenn Laufzeitprüfungen deaktiviert sind.

unowned vs weak

unowned hat tatsächlich eine viel einfachere Implementierung als weak. Native Swift-Objekte tragen zwei Referenzzähler, und unowned Referenzen erhöhen den Unowned Referenzzähler anstelle des starken Referenzzählers. Das Objekt wird deinitialisiert, wenn sein starker Referenzzähler null erreicht, aber es wird tatsächlich erst dealloziert, wenn der unowned Referenzzähler ebenfalls null erreicht. Dadurch wird der Speicher etwas länger gehalten, wenn es unowned-Referenzen gibt, aber das ist normalerweise kein Problem bei Verwendung von unowned, da die zugehörigen Objekte sowieso fast gleichzeitig existieren sollten, und es ist einfacher und weniger aufwändig als die Implementierung mit Seitentabellen für Nullsetzende schwache Referenzen.

Update: In modernem Swift weak intern verwendet den gleichen Mechanismus wie unowned. Deshalb ist dieser Vergleich falsch, weil er Objective-C weak mit Swift unonwed vergleicht.

Gründe

Was ist der Zweck, das Gedächtnis nach Erreichen der Zählreferenzen zu 0 am Leben zu erhalten? Was passiert, wenn der Code versucht, etwas mit dem Objekt über eine unowned-Referenz zu tun, nachdem es deinitialisiert wurde?

Der Speicher bleibt am Leben, damit seine Retain-Zähler weiter verfügbar sind. Auf diese Weise, wenn jemand versucht, eine starke Referenz zum unowned-Objekt zu behalten, kann das Laufzeitsystem überprüfen, ob der starke Referenzzähler größer als null ist, um sicherzustellen, dass es sicher ist, das Objekt zu behalten.

Was passiert mit besitzenden oder unowned-Referenzen, die vom Objekt gehalten werden? Ist ihre Lebensdauer vom Objekt entkoppelt, wenn es deinitialisiert wird oder wird ihr Gedächtnis auch behalten, bis das Objekt dealloziert wird, nachdem die letzte unowned-Referenz freigegeben wird?

Alle Ressourcen, die vom Objekt besessen werden, werden freigegeben, sobald die letzte starke Referenz des Objekts freigegeben wird und sein deinit ausgeführt wird. Unowned Referenzen halten nur den Speicher am Leben - abgesehen vom Header mit den Referenzzählungen, ist sein Inhalt unnütz.

Aufgeregt, huh?

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X