ios - unowned - swift weak self




Ist es sicher, Unwrap-Variablen zu erzwingen, auf die optional in derselben Codezeile zugegriffen wurde? (4)

someFunction(completion: { [weak self] in
    self?.variable = self!.otherVariable
})

Ist das immer sicher? Ich greife auf das optionale self zu Beginn der Anweisung zu und persönlich gehe ich davon aus, dass der zweite Teil dieser Anweisung niemals ausgeführt wird, wenn self nil . Ist das wahr? Wenn self Tat gleich nil , wird der zweite Teil niemals passieren? Und es wird nie passieren, dass das self während dieser einzelnen Codezeile "nilled" sein könnte?


Ist das immer sicher?

Nein . Du machst nicht den "schwach-starken Tanz". TU es! Wann immer Sie ein weak self , sollten Sie das Optionale sicher auspacken und dann nur auf das Ergebnis des Auspackens verweisen - wie folgt:

someFunction(completion: { [weak self] in
    if let sself = self { // safe unwrap
        // now refer only to `sself` here
        sself.variable = sself.otherVariable
        // ... and so on ...
    }
})

Nein, das ist nicht sicher

Wie von @Hamish in einem Kommentar erläutert, beschreibt Joe Groff , Swift Compiler Engineer , dass es keine Garantie dafür gibt, dass ein starker Bezug für die Dauer der RHS-Bewertung gehalten wird.

Reihenfolge der Vorgänge bestätigen

Rod_Brown:

Hallo,

Ich frage mich, wie sicher eine Art Zugriff auf eine schwache Variable ist:

class MyClass {

    weak var weakProperty: MyWeakObject?

    func perform() {
        // Case 1
        weakProperty?.variable = weakProperty!.otherVariable

        // Case 2
        weakProperty?.performMethod(weakProperty!)
    }
}

weakProperty Swift in den beiden oben genannten Fällen garantiert, dass die weakProperty an diesen Positionen gewaltsam ausgepackt werden kann?

Ich bin neugierig auf die Garantien, die Swift über den Zugriff während der optionalen Verkettung gibt. ZB sind die weakProperty! Accessoren, die garantiert nur ausgelöst werden, wenn die optionale Verkettung zuerst feststellt, dass der Wert bereits nicht nil ?

Wird außerdem sichergestellt, dass das schwache Objekt für die Dauer dieser Auswertung beibehalten wird, oder kann die schwache Variable möglicherweise zwischen dem optionalen Zugriff und der aufgerufenen Methode aufheben?

Joe_Groff:

Dies ist nicht garantiert. Releases können so optimiert werden, dass sie zu einem früheren Zeitpunkt geschehen können, zu einem beliebigen Zeitpunkt nach der letzten formalen Verwendung der starken Referenz. Da die starke Referenz, die geladen wird, um die weakProperty?.variable nicht mehr verwendet wird, gibt es nichts, was sie am Leben hält, sodass sie sofort freigegeben werden könnte. Wenn im Getter für Variable Nebenwirkungen auftreten, die die weakProperty der weakProperty des Objekts, auf das von weakProperty verwiesen wird, aus dem schwachen Verweis aufheben , würde dies dazu führen , dass die Aufhebung der Zwangsabwicklung auf der rechten Seite fehlschlägt . Verwenden Sie if let, um die schwache Referenz zu testen, und referenzieren Sie die starke Referenzgrenze von if let:

if let property = weakProperty {
  property.variable = property.otherVariable
  property.performMethod(property)
}

Dies sollte sicherer und auch effizienter sein, da die schwache Referenz nicht viermal, sondern einmalig geladen und getestet wird.

Angesichts der Antwort, die Joe Groff oben zitiert hat, ist meine bisherige Antwort eher umstritten, aber ich werde sie hier als möglicherweise interessante (wenn auch gescheiterte) Reise in die Tiefen des Swift-Compilers belassen.

Historische Antwort auf ein falsches Schlussargument, aber trotzdem durch eine interessante Reise

Diese Antwort stütze ich auf meinen Kommentar an @appzYourLife: s gelöschte Antwort:

Dies ist reine Spekulation, aber angesichts der etwas engen Verbindung zwischen vielen der erfahrenen Swift-Core-Entwickler und C ++: s Boost lib würde ich davon ausgehen, dass ein weak Bezug für die gesamte Lebensdauer des Ausdrucks an einen starken gebunden ist, wenn dieser zuweist / mutiert etwas in sich self , ähnlich wie das explizit verwendete std::weak_ptr::lock() des C ++ - Gegenstücks.

Sehen wir uns Ihr Beispiel an, in dem self durch eine weak Referenz erfasst wurde und beim Zugriff auf die linke Seite des Zuweisungsausdrucks nicht gleich nil

self?.variable = self!.otherVariable
/* ^             ^^^^^-- what about this then?
   |
    \-- we'll assume this is a success */

Wir können uns die zugrunde liegende Behandlung weak (Swift) Referenzen in der Swift-Laufzeit genauer swift/include/swift/Runtime/HeapObject.h : swift/include/swift/Runtime/HeapObject.h :

/// Load a value from a weak reference.  If the current value is a
/// non-null object that has begun deallocation, returns null;
/// otherwise, retains the object before returning.
///
/// \param ref - never null
/// \return can be null
SWIFT_RUNTIME_EXPORT
HeapObject *swift_weakLoadStrong(WeakReference *ref);

Der Schlüssel hier ist der Kommentar

Wenn der aktuelle Wert ein Nicht-NULL-Objekt ist, das mit der Freigabe begonnen hat, wird NULL zurückgegeben. Andernfalls bleibt das Objekt vor der Rückgabe erhalten .

Da dies auf dem Backend-Laufzeitcodekommentar basiert, ist es immer noch etwas spekulativ, aber ich würde sagen, dass das Obige impliziert, dass, wenn versucht wird, auf den Wert zuzugreifen, auf den eine weak Referenz verweist, diese Referenz tatsächlich während der gesamten Lebensdauer als stark beibehalten wird des Anrufs ( "... bis zur Rückkehr" ).

Um zu versuchen, den "etwas spekulativen" Teil von oben zu erlösen, können wir uns weiter damit beschäftigen, wie Swift den Zugriff auf einen Wert über eine weak Referenz handhabt. Aus dem folgenden @idmean-Kommentar (der den generierten SIL-Code für ein Beispiel wie das OP: s untersucht) wissen wir, dass die Funktion swift_weakLoadStrong(...) aufgerufen wird.

Wir schauen uns also zunächst die Implementierung der Funktion swift_weakLoadStrong(...) in swift/stdlib/public/runtime/HeapObject.cpp und sehen, wo wir dorthin gelangen:

HeapObject *swift::swift_weakLoadStrong(WeakReference *ref) {
  return ref->nativeLoadStrong();
}

Die Implementierung der nativeLoadStrong() Methode von WeakReference finden wir in swift/include/swift/Runtime/HeapObject.h

HeapObject *nativeLoadStrong() {
  auto bits = nativeValue.load(std::memory_order_relaxed);
  return nativeLoadStrongFromBits(bits);
}

Aus derselben Datei die Implementierung von nativeLoadStrongFromBits(...) :

HeapObject *nativeLoadStrongFromBits(WeakReferenceBits bits) {
  auto side = bits.getNativeOrNull();
  return side ? side->tryRetain() : nullptr;
}

tryRetain() ist eine Methode von HeapObjectSideTableEntry (die für die Zustandsmaschine des HeapObjectSideTableEntry unerlässlich ist). Die Implementierung findet sich in swift/stdlib/public/SwiftShims/RefCount.h

HeapObject* tryRetain() {
  if (refCounts.tryIncrement())
    return object.load(std::memory_order_relaxed);
  else
    return nullptr;
}

Die Implementierung der tryIncrement() Methode des RefCounts Typs (hier über eine Instanz einer typedef : ed-Spezialisierung davon aufgerufen) befindet sich in derselben Datei wie oben :

// Increment the reference count, unless the object is deiniting.
bool tryIncrement() {
  ...
}

Ich glaube, der Kommentar hier reicht aus, um diese Methode als Endpunkt zu verwenden: Wenn das Objekt nicht deinitiert wird (was wir oben angenommen haben, ist dies nicht der lhs , da die lhs der Zuweisung im Beispiel von OPs angenommen wird) erfolgreich), wird die (starke) Referenzzählung für das Objekt erhöht und ein HeapObject Zeiger (gesichert durch ein starkes Referenzzählerinkrement) an den Zuweisungsoperator übergeben. Wir müssen nicht untersuchen, wie die entsprechende Referenzzählerabnahme schließlich am Ende der Zuweisung durchgeführt wird, aber wir müssen über die Spekulation hinaus wissen, dass das mit der weak Referenz verknüpfte Objekt während der gesamten Dauer der Zuweisung als starkes Objekt beibehalten wird Es wurde zum Zeitpunkt des Zugriffs auf die linke Seite nicht freigegeben / freigegeben (in diesem Fall wird die rechte Seite niemals verarbeitet, wie in der Antwort von @MartinR erläutert).


Die optionale Verkettung von "The Swift Programming Language" zeigt das folgende Beispiel:

 let john = Person()
 // ...
 let someAddress = Address()
 // ...
 john.residence?.address = someAddress

gefolgt von (Hervorhebung hinzugefügt):

In diesem Beispiel schlägt der Versuch fehl, die Adresseneigenschaft von john.residence festzulegen, da john.residence derzeit null ist.

Die Zuweisung ist Teil der optionalen Verkettung, dh es wird kein Code auf der rechten Seite des Operators = ausgewertet.

Auf Ihren Fall angewendet:

self?.variable = self!.otherVariable

Die rechte Seite wird nicht ausgewertet, wenn self nil . Daher die Antwort auf Ihre Frage

Wenn Selbst in der Tat gleich Null ist, wird der zweite Teil niemals passieren?

ist ja". Zur zweiten Frage

Und es wird nie passieren, dass das Selbst während dieser einzelnen Codezeile "nilled" sein könnte?

Meine ursprüngliche Annahme war, dass das self einmal festgelegt wurde != nil , ein starker Bezug zu sich self! Während der gesamten Auswertung wird die Aussage gehalten, damit dies nicht passieren kann. Dies wird jedoch nicht (wie @Hamish hervorgehoben hat) garantiert. Apple-Ingenieur Joe Groff schreibt bei Bestätigen der Reihenfolge der Vorgänge im Swift-Forum:

Dies ist nicht garantiert. Releases können so optimiert werden, dass sie zu einem früheren Zeitpunkt geschehen können, zu einem beliebigen Zeitpunkt nach der letzten formalen Verwendung der starken Referenz. Da die starke Referenz, die geladen wird, um die weakProperty?.variable nicht mehr verwendet wird, gibt es nichts, was sie am Leben hält, sodass sie sofort freigegeben werden könnte.
Wenn im Getter für Variable irgendwelche Nebenwirkungen auftreten, die dazu führen, dass das Objekt, auf das von weakProperty , freigegeben wird, und die schwache Referenz aufgehoben wird, würde dies dazu führen, dass das Force-Unwrap auf der rechten Seite fehlschlägt. Verwenden Sie if let, um die schwache Referenz zu testen, und referenzieren Sie die starke Referenzgrenze von if let


VOR KORREKTUR:

Ich denke, andere haben die Details Ihrer Frage viel besser beantwortet, als ich könnte.

Aber abgesehen vom Lernen. Wenn Sie wirklich möchten, dass Ihr Code zuverlässig funktioniert, tun Sie dies am besten:

someFunction(completion: { [weak self] in
    guard let _ = self else{
        print("self was nil. End of discussion")
        return
    }
    print("we now have safely 'captured' a self, no need to worry about this issue")
    self?.variable = self!.otherVariable
    self!.someOthervariable = self!.otherVariable
}

NACH DER KORREKTUR.

Dank der untenstehenden Erklärung von MartinR habe ich sehr viel gelernt.

Lesen von diesem großartigen Beitrag über die Schließung . Ich dachte sklavisch, wenn Sie etwas in Klammern [] , bedeutet das, dass es erfasst wird und sein Wert sich nicht ändert. Aber das einzige, was wir in den Klammern tun, ist, dass wir es weak und es uns bewusst machen, dass der Wert nil . Hätten wir etwas wie [x = self] , hätten wir es erfolgreich erfasst, aber dann hätten wir noch das Problem, einen starken Zeiger auf self selbst zu halten und einen Gedächtniszyklus zu schaffen. (Dies ist insofern interessant, als es eine sehr schmale Linie ist, von der Erstellung eines Gedächtniszyklus bis hin zum Absturz, weil der Wert aufgehoben wird, weil Sie ihn nicht mehr gewürdigt haben).

Also zum Abschluss:

  1. [capturedSelf = self]

    schafft einen Gedächtniszyklus. Nicht gut!

  2. [weak self] 
    in guard let _ = self else{
    return
    } 

    (kann zum Absturz führen, wenn Sie self Abwickeln zwingen) Die guard let sie völlig unbrauchbar werden. Denn schon die nächste Zeile kann noch self nil . Nicht gut!

  3. [weak self] 
    self?.method1()

    (kann zu Abstürzen führen, wenn Sie das Abwickeln des self Wraps erzwingen. Wird durchlaufen, wenn das self nicht- nil . Wird sicher fehlschlagen, wenn das self Ergebnis nil ist.) Dies ist wahrscheinlich das, was Sie wollen. Das ist gut !

  4. [weak self] in 
    guard let strongSelf = self else{ 
    return
    } 

    Wird sicher fehlschlagen, wenn das System self freigegeben wurde, oder fortfahren, wenn nicht gleich nil . Aber es besiegt irgendwie den Zweck, denn Sie sollten nicht mit dem self kommunizieren müssen, wenn es seine eigene Referenz entfernt hat. Ich kann mir keinen guten Anwendungsfall dafür vorstellen. Das ist wahrscheinlich nutzlos!







optional