Schnelle Erfassungslisten: Was ist der Unterschied zwischen schwachen, starken und nicht besessenen Links?


Joseph Wright, "Gefangener" - Illustration der "starken" Gefangennahme

Die Liste der "erfassten" Werte befindet sich vor der Liste der Schließungsparameter und kann Werte aus dem Bereich auf drei verschiedene Arten "erfassen": über die Links "stark", "schwach" oder "nicht besessen". Wir verwenden es oft, hauptsächlich um starke Referenzzyklen zu vermeiden („starke Referenzzyklen“, auch bekannt als „Haltezyklen“).
Es kann für einen unerfahrenen Entwickler schwierig sein, sich für eine Methode zu entscheiden, sodass Sie viel Zeit damit verbringen können, zwischen „stark“ und „schwach“ oder zwischen „schwach“ und „nicht besessen“ zu wählen. Mit der Zeit werden Sie jedoch feststellen, dass dies die richtige Wahl ist - nur einer.

Erstellen Sie zunächst eine einfache Klasse:

class Singer { func playSong() { print("Shake it off!") } } 

Dann schreiben wir eine Funktion, die eine Instanz der Singer- Klasse erstellt und einen Abschluss zurückgibt, der die playSong () -Methode der Singer- Klasse aufruft :

 func sing() -> () -> Void { let taylor = Singer() let singing = { taylor.playSong() return } return singing } 

Schließlich können wir sing () überall aufrufen, um das Ergebnis des Spielens von playSong () zu erhalten.

 let singFunction = sing() singFunction() 


Infolgedessen wird die Zeile „Shake it off!“ Angezeigt.

Starke Erfassung


Sofern Sie keine explizite Erfassungsmethode angeben, verwendet Swift eine „starke“ Erfassung. Dies bedeutet, dass der Verschluss die verwendeten externen Werte erfasst und diese niemals freigibt.

Schauen wir uns noch einmal die Funktion sing () an

 func sing() -> () -> Void { let taylor = Singer() let singing = { taylor.playSong() return } return singing } 

Die Taylor-Konstante wird innerhalb einer Funktion definiert, sodass unter normalen Umständen ihr Platz freigegeben wird, sobald die Funktion ihre Arbeit beendet hat. Diese Konstante wird jedoch innerhalb des Verschlusses verwendet, was bedeutet, dass Swift seine Anwesenheit automatisch sicherstellt, solange der Verschluss selbst existiert, auch nach dem Ende der Funktion.
Dies ist eine "starke" Erfassung in Aktion. Wenn Swift zulässt, dass Taylor freigegeben wird, ist das Aufrufen eines Abschlusses unsicher - die Methode taylor.playSong () ist nicht mehr gültig.

"Schwache" Erfassung (schwache Erfassung)


Mit Swift können wir eine „ Erfassungsliste “ erstellen, um zu bestimmen, wie die verwendeten Werte erfasst werden. Eine Alternative zur „starken“ Erfassung ist „schwach“ und ihre Anwendung führt zu folgenden Konsequenzen:

1. „Schwach“ erfasste Werte werden vom Verschluss nicht gehalten und können daher freigegeben und auf Null gesetzt werden .

2. Infolge des ersten Absatzes sind „schwach“ erfasste Werte in Swift immer optional .
Wir modifizieren unser Beispiel mit einer „schwachen“ Erfassung und sehen sofort den Unterschied.

 func sing() -> () -> Void { let taylor = Singer() let singing = { [weak taylor] in taylor?.playSong() return } return singing } 

[schwacher Taylor] - Dies ist unsere „ Erfassungsliste “, ein spezieller Teil der Abschlusssyntax, in der wir Anweisungen geben, wie die Werte erfasst werden sollen. Hier sagen wir, dass Taylor "schwach" erfasst werden sollte, also müssen wir Taylor verwenden? .PlaySong () - jetzt ist es optional , da es jederzeit auf Null gesetzt werden kann.

Wenn Sie diesen Code jetzt ausführen, werden Sie feststellen, dass der Aufruf von singFunction () nicht mehr zu einer Nachricht führt. Der Grund dafür ist, dass Taylor nur in sing () existiert und der von dieser Funktion zurückgegebene Verschluss Taylor nicht „stark“ in sich hält.

Versuchen Sie nun, taylor? .PlaySong () in taylor! .PlaySong () zu ändern . Dies führt zu einem erzwungenen Auspacken von Taylor im Verschluss und dementsprechend zu einem schwerwiegenden Fehler (Auspacken von Inhalten, die nichts enthalten).

"Besitzerlose" Erfassung (nicht besessene Erfassung)


Eine Alternative zur "schwachen" Erfassung ist "inhaberlos".

 func sing() -> () -> Void { let taylor = Singer() let singing = { [unowned taylor] in taylor.playSong() return } return singing } 

Dieser Code endet auf ähnliche Weise abnormal mit dem oben gezeigten optional bereitgestellten optionalen - nicht besessenen Taylor sagt: "Ich weiß sicher, dass Taylor für die Dauer des Abschlusses existieren wird, daher muss ich ihn nicht im Speicher behalten." Tatsächlich wird Taylor fast sofort veröffentlicht und dieser Code stürzt ab.

Gehen Sie daher äußerst vorsichtig mit nicht besessenen Gegenständen um .

Häufige Probleme


Entwickler haben vier Probleme, wenn sie die Werterfassung in Abschlüssen verwenden:

1. Schwierigkeiten mit der Position der Erfassungsliste in dem Fall, in dem der Abschluss Parameter annimmt


Dies ist ein häufiges Problem, auf das Sie möglicherweise zu Beginn der Untersuchung von Verschlüssen stoßen, aber glücklicherweise wird Swift uns in diesem Fall helfen.

Wenn Sie die Erfassungsliste und die Abschlussparameter zusammen verwenden, wird die Erfassungsliste in eckigen Klammern angezeigt, dann die Abschlussparameter und dann das Schlüsselwort in, das den Beginn des Abschlusskörpers markiert.

 writeToLog { [weak self] user, message in self?.addToLog("\(user) triggered event: \(message)") } 

Der Versuch, nach dem Schließen eine Erfassungsliste zu erstellen, führt zu einem Kompilierungsfehler.

2. Die Entstehung eines Zyklus starker Verbindungen, der zu einem Speicherverlust führt


Wenn eine Entität A eine Entität B hat und umgekehrt, haben Sie eine Situation, die als „Aufbewahrungszyklus“ bezeichnet wird.

Betrachten Sie als Beispiel den Code:

 class House { var ownerDetails: (() -> Void)? func printDetails() { print("This is a great house.") } deinit { print("I'm being demolished!") } } 

Wir haben die House- Klasse definiert, die eine Eigenschaft (Closure), eine Methode und einen De-Initialisierer enthält, der eine Nachricht anzeigt, wenn eine Instanz der Klasse zerstört wird.

Erstellen Sie nun eine Owner- Klasse ähnlich der vorherigen, mit der Ausnahme, dass ihre Closure-Eigenschaft Informationen zum Haus enthält.

 class Owner { var houseDetails: (() -> Void)? func printDetails() { print("I own a house.") } deinit { print("I'm dying!") } } 

Erstellen Sie nun Instanzen dieser Klassen im do- Block. Wir brauchen keinen catch-Block, aber die Verwendung eines do- Blocks zerstört die Instanzen direkt nach}

 print("Creating a house and an owner") do { let house = House() let owner = Owner() } print("Done") 

Als Ergebnis werden Meldungen angezeigt: "Ein Haus und einen Eigentümer schaffen", "Ich sterbe!", "Ich werde abgerissen!", Dann "Fertig" - alles funktioniert wie es sollte.

Erstellen Sie nun eine Schleife mit starken Links.

 print("Creating a house and an owner") do { let house = House() let owner = Owner() house.ownerDetails = owner.printDetails owner.houseDetails = house.printDetails } print("Done") 

Nun erscheint die Meldung „Haus und Eigentümer erstellen“ und dann „Fertig“. Deinitializer werden nicht aufgerufen.

Dies geschah aufgrund der Tatsache, dass das Haus eine Eigenschaft hat, die auf den Eigentümer zeigt, und der Eigentümer eine Eigenschaft hat, die auf das Haus zeigt. Daher kann keiner von ihnen sicher freigegeben werden. In einer realen Situation führt dies zu Speicherverlusten, die zu einer schlechten Leistung und sogar zum Absturz der Anwendung führen.

Um die Situation zu beheben, müssen wir einen neuen Abschluss erstellen und in ein oder zwei Fällen eine „schwache“ Erfassung verwenden, wie folgt:

 print("Creating a house and an owner") do { let house = House() let owner = Owner() house.ownerDetails = { [weak owner] in owner?.printDetails() } owner.houseDetails = { [weak house] in house?.printDetails() } } print("Done") 

Es ist nicht erforderlich, beide erfassten Werte zu deklarieren. Es reicht aus, dies an einem Ort zu tun. Dadurch kann Swift bei Bedarf beide Klassen zerstören.

In realen Projekten tritt die Situation eines solch offensichtlichen Zyklus starker Verbindungen selten auf, aber dies spricht umso mehr für die Bedeutung der Verwendung einer „schwachen“ Erfassung mit kompetenter Entwicklung.

3. Die versehentliche Verwendung starker Links, normalerweise beim Erfassen mehrerer Werte


Swift verwendet standardmäßig einen starken Griff, was zu unerwartetem Verhalten führen kann.
Betrachten Sie den folgenden Code:

 func sing() -> () -> Void { let taylor = Singer() let adele = Singer() let singing = { [unowned taylor, adele] in taylor.playSong() adele.playSong() return } return singing } 

Jetzt haben wir zwei Werte, die vom Abschluss erfasst werden, und wir verwenden beide auf die gleiche Weise. Es wird jedoch nur Taylor als nicht besessen erfasst - Adele wird stark erfasst, da das nicht besessene Schlüsselwort für jeden erfassten Wert verwendet werden muss.

Wenn Sie dies absichtlich getan haben, ist alles in Ordnung. Wenn Sie jedoch möchten, dass beide Werte als "nicht besessen " erfasst werden , benötigen Sie Folgendes:

 [unowned taylor, unowned adele] 

4. Kopieren Sie Abschlüsse und teilen Sie erfasste Werte


Der letzte Fall, über den Entwickler stolpern, ist das Kopieren von Fehlern, da die von ihnen erfassten Daten für alle Kopien des Fehlers verfügbar werden.
Stellen Sie sich ein Beispiel für einen einfachen Abschluss vor, der die außerhalb des Abschlusses deklarierte Ganzzahlvariable numberOfLinesLogged erfasst, damit wir den Wert erhöhen und ihn bei jedem Aufruf des Abschlusses ausdrucken können:

 var numberOfLinesLogged = 0 let logger1 = { numberOfLinesLogged += 1 print("Lines logged: \(numberOfLinesLogged)") } logger1() 

Daraufhin wird die Meldung "Linien protokolliert: 1" angezeigt.
Jetzt erstellen wir eine Kopie des Abschlusses, die die erfassten Werte zusammen mit dem ersten Abschluss teilt. Wenn wir also den ursprünglichen Abschluss oder seine Kopie aufrufen, sehen wir den wachsenden Wert der Variablen.

 let logger2 = logger1 logger2() logger1() logger2() 

Dadurch werden die Meldungen "Zeilen protokolliert: 1" ... "Zeilen protokolliert: 4" ausgedruckt, da logger1 und logger2 auf dieselbe erfasste Variable numberOfLinesLogged verweisen.

Wann man ein "starkes" Capture verwendet, "schwach" und "inhaberlos"


Nachdem wir nun verstanden haben, wie alles funktioniert, versuchen wir zusammenzufassen:

1. Wenn Sie sicher sind, dass der erfasste Wert beim Schließen niemals Null wird , können Sie die Option "Nicht besessene Erfassung" verwenden . Dies ist eine seltene Situation, in der die Verwendung einer „schwachen“ Erfassung zusätzliche Schwierigkeiten verursachen kann, selbst wenn die Verwendung von Guard Let auf einen schwach erfassten Wert innerhalb des Verschlusses erfolgt.

2. Wenn Sie einen Zyklus starker Verbindungen haben (Entität A besitzt Entität B und Entität B besitzt Entität A), müssen Sie in einem der Fälle die „schwache Erfassung“ verwenden . Es muss berücksichtigt werden, welche der beiden Entitäten zuerst freigegeben wird. Wenn also Ansichtscontroller A Ansichtscontroller B darstellt, kann Ansichtscontroller B eine „schwache“ Verknüpfung zurück zu „A“ enthalten.

3. Wenn die Möglichkeit eines Zyklus starker Links ausgeschlossen ist, können Sie "starke" Erfassung ( "starke Erfassung" ) verwenden. Wenn Sie beispielsweise eine Animation ausführen, wird das Selbst innerhalb des Verschlusses, der die Animation enthält, nicht blockiert, sodass Sie eine starke Bindung verwenden können.

4. Wenn Sie sich nicht sicher sind, beginnen Sie mit einer „schwachen“ Bindung und ändern Sie diese nur bei Bedarf.

Optional - Offizieller Swift Guide:
Kurzschlüsse
Automatische Linkzählung

Source: https://habr.com/ru/post/de444336/


All Articles