Ist Swift Pass By Value oder Pass By Reference

100

Ich bin wirklich neu in Swift und habe gerade gelesen, dass Klassen als Referenz übergeben und Arrays / Strings usw. kopiert werden.

Ist die Referenzübergabe dieselbe wie in Objective-C oder Java, in der Sie tatsächlich eine Referenz übergeben, oder ist sie eine ordnungsgemäße Referenzübergabe?

gran_profaci
quelle
"Ist die Referenzübergabe dieselbe wie in Objective-C oder Java?" Weder Objective-C noch Java haben eine Referenzübergabe.
Newacct
2
Ja. Ich weiß das. Sie gehen nicht als Referenz vorbei. Sie übergeben die Referenz als Wert. Ich nahm an, dass dies bei der Beantwortung bekannt war.
gran_profaci
Java wird als Wert übergeben, nicht als Referenz.
6.

Antworten:

166

Arten von Dingen in Swift

Die Regel lautet:

  • Klasseninstanzen sind Referenztypen (dh Ihre Referenz auf eine Klasseninstanz ist effektiv ein Zeiger )

  • Funktionen sind Referenztypen

  • Alles andere ist ein Werttyp ; "alles andere" bedeutet einfach Instanzen von Strukturen und Instanzen von Aufzählungen, denn das ist alles, was es in Swift gibt. Arrays und Strings sind beispielsweise Strukturinstanzen. Sie können einen Verweis auf eines dieser Dinge (als Funktionsargument) übergeben, inoutindem Sie die Adresse verwenden und verwenden, wie newacct hervorgehoben hat. Der Typ ist jedoch selbst ein Werttyp.

Was Referenztypen für Sie bedeuten

Ein Referenztypobjekt ist in der Praxis besonders, weil:

  • Die bloße Zuweisung oder Übergabe an die Funktion kann zu mehreren Verweisen auf dasselbe Objekt führen

  • Das Objekt selbst ist veränderbar, selbst wenn der Verweis darauf eine Konstante ist ( letentweder explizit oder implizit).

  • Eine Mutation des Objekts wirkt sich auf dieses Objekt aus, wie aus allen Verweisen darauf hervorgeht.

Das können Gefahren sein, also pass auf. Andererseits ist das Übergeben eines Referenztyps eindeutig effizient, da nur ein Zeiger kopiert und übergeben wird, was trivial ist.

Welche Werttypen bedeuten für Sie?

Das Übergeben eines Werttyps ist eindeutig "sicherer" und letbedeutet, was darin steht: Sie können eine Strukturinstanz oder eine Enum-Instanz nicht über eine letReferenz mutieren . Andererseits wird diese Sicherheit erreicht, indem eine separate Kopie des Wertes erstellt wird, nicht wahr? Ist das Übergeben eines Werttyps nicht potenziell teuer?

Ja und nein. Es ist nicht so schlimm, wie Sie vielleicht denken. Wie Nate Cook gesagt hat, bedeutet das Übergeben eines Werttyps nicht unbedingt das Kopieren, da let(explizit oder implizit) Unveränderlichkeit garantiert wird, sodass nichts kopiert werden muss. Und selbst in eine vorübergehenden varReferenz bedeutet nicht , dass die Dinge werden kopiert werden, nur , dass sie können , wenn notwendig sein (weil es eine Mutation ist). Die Dokumentation rät Ihnen ausdrücklich, Ihre Schlüpfer nicht in eine Wendung zu bringen.

matt
quelle
6
"Klasseninstanzen werden als Referenz übergeben. Funktionen werden als Referenz übergeben" Nein. Es wird als Wert übergeben, wenn der Parameter nicht inoutunabhängig vom Typ ist. Ob etwas als Referenz übergeben wird, ist orthogonal zu Typen.
Newacct
4
@newacct Nun, natürlich hast du im engeren Sinne Recht! Streng genommen sollte man sagen , dass alles Pass-by-Wert , sondern dass ENUM - Instanzen und struct Instanzen sind Werttypen und die Klasseninstanzen und Funktionen sind Referenztypen . Siehe zum Beispiel developer.apple.com/swift/blog/?id=10 - Siehe auch developer.apple.com/library/ios/documentation/Swift/Conceptual/… . Ich denke jedoch, dass das, was ich gesagt habe, mit dem General übereinstimmt Sinn der Worte bedeuten.
Matt
6
Richtig, und Werttypen / Referenztypen sollten nicht mit Wertübergabe / Referenzübergabe verwechselt werden, da Werttypen als Wert oder als Referenz übergeben werden können und Referenztypen auch als Wert oder als Referenz übergeben werden können.
Newacct
1
@newacct sehr nützliche Diskussion; Ich schreibe meine Zusammenfassung neu, damit sie nicht irreführt.
Matt
43

Es wird immer als Wert übergeben, wenn der Parameter nicht vorhanden ist inout.

Es ist immer eine Referenzübergabe, wenn der Parameter ist inout. Dies ist jedoch etwas kompliziert, da Sie das explizit verwenden müssen& Operator für das Argument wenn Sie an einen inoutParameter übergeben, sodass er möglicherweise nicht der herkömmlichen Definition von Referenzübergabe entspricht, bei der Sie die Variable direkt übergeben.

newacct
quelle
3
Diese Antwort, kombiniert mit der von Nate Cook, war für mich (aus C ++) klarer, dass selbst ein "Referenztyp" außerhalb des Funktionsumfangs nicht geändert wird , es sei denn, Sie geben ihn explizit an (mithilfe von inout)
Gobe
10
inoutwird eigentlich nicht als Referenz übergeben, sondern als Kopie kopiert. Es wird nur garantiert, dass nach dem Funktionsaufruf dem ursprünglichen Argument ein geänderter Wert zugewiesen wird. In-Out-Parameter
Dalija Prasnikar
obwohl es wahr ist, dass alles als Wert übergeben wird. Die Eigenschaften der Referenztypen können innerhalb der Funktion geändert werden, da die Kopie auf dieselbe Instanz verweist.
MrAn3
42

Alles in Swift wird standardmäßig als "Kopie" übergeben. Wenn Sie also einen Werttyp übergeben, erhalten Sie eine Kopie des Werts, und wenn Sie einen Referenztyp übergeben, erhalten Sie eine Kopie der Referenz mit allem, was dazu gehört. (Das heißt, die Kopie der Referenz zeigt immer noch auf dieselbe Instanz wie die ursprüngliche Referenz.)

Ich verwende Angstzitate um die "Kopie" oben, weil Swift viel optimiert; Wo immer möglich, wird es erst kopiert, wenn eine Mutation oder die Möglichkeit einer Mutation vorliegt. Da Parameter standardmäßig unveränderlich sind, bedeutet dies, dass die meiste Zeit tatsächlich keine Kopie stattfindet.

Nate Cook
quelle
Für mich ist dies die beste Antwort, da klargestellt wurde, dass beispielsweise Instanzeigenschaften innerhalb der Funktion geändert werden können, selbst wenn der Parameter eine Kopie ist (Wert übergeben), da er auf dieselbe Referenz verweist.
MrAn3
9

Hier ist ein kleines Codebeispiel zum Übergeben als Referenz. Vermeiden Sie dies, es sei denn, Sie haben einen starken Grund dazu.

func ComputeSomeValues(_ value1: inout String, _ value2: inout Int){
    value1 = "my great computation 1";
    value2 = 123456;
}

Nennen Sie es so

var val1: String = "";
var val2: Int = -1;
ComputeSomeValues(&val1, &val2);
Chris Amelinckx
quelle
Warum sollten Sie dies vermeiden?
Brainless
1
@Brainless, weil es dem Code unnötige Komplexität hinzufügt. Es ist am besten, Parameter aufzunehmen und ein einzelnes Ergebnis zurückzugeben. Dies zu tun, signalisiert normalerweise ein schlechtes Design. Eine andere Möglichkeit ist, dass versteckte Nebenwirkungen in übergebenen referenzierten Variablen für den Aufrufer nicht transparent sind.
Chris Amelinckx
Dies gilt nicht als Referenz. inoutist ein Operator zum Kopieren und Kopieren. Es wird zuerst in das Objekt kopiert und dann das ursprüngliche Objekt überschrieben, nachdem die Funktion zurückgekehrt ist. Während es gleich scheinen mag, gibt es subtile Unterschiede.
Hannes Hertach
7

Das Apple Swift Developer- Blog enthält einen Beitrag mit dem Namen " Wert- und Referenztypen" , der eine klare und detaillierte Diskussion zu diesem Thema bietet.

Zitieren:

Typen in Swift fallen in eine von zwei Kategorien: Erstens „Werttypen“, bei denen jede Instanz eine eindeutige Kopie ihrer Daten aufbewahrt, die normalerweise als Struktur, Aufzählung oder Tupel definiert sind. Der zweite, "Referenztypen", bei denen Instanzen eine einzelne Kopie der Daten gemeinsam nutzen und der Typ normalerweise als Klasse definiert wird.

Der Swift-Blogbeitrag erklärt die Unterschiede weiterhin anhand von Beispielen und schlägt vor, wann Sie sie übereinander verwenden würden.

warum weiß
quelle
1
Dies beantwortet die Frage nicht. Die Frage bezieht sich auf Pass-by-Value vs. Pass-by-Reference, die vollständig orthogonal zu Werttypen vs. Referenztypen ist.
Jörg W Mittag
2

Klassen werden von Referenzen übergeben und andere werden standardmäßig als Wert übergeben. Sie können mit dem inoutSchlüsselwort als Referenz übergeben .

Bahram Zangene
quelle
Das ist falsch. inoutist ein Operator zum Kopieren und Kopieren. Es wird zuerst in das Objekt kopiert und dann das ursprüngliche Objekt überschrieben, nachdem die Funktion zurückgekehrt ist. Während es gleich scheinen mag, gibt es subtile Unterschiede.
Hannes Hertach
2

Wenn Sie inout mit einem Infix-Operator wie + = verwenden, kann das Symbol & address ignoriert werden. Ich denke, der Compiler geht davon aus, dass er als Referenz übergeben wird.

extension Dictionary {
    static func += (left: inout Dictionary, right: Dictionary) {
        for (key, value) in right {
            left[key] = value
        }
    }
}

origDictionary + = newDictionaryToAdd

Und schön, dieses Wörterbuch 'hinzufügen' schreibt auch nur auf die ursprüngliche Referenz, also großartig zum Sperren!

Jules Burt
quelle
2

Klassen und Strukturen

Einer der wichtigsten Unterschiede zwischen Strukturen und Klassen besteht darin, dass Strukturen immer kopiert werden, wenn sie in Ihrem Code herumgereicht werden, Klassen jedoch als Referenz übergeben werden.

Verschlüsse

Wenn Sie einer Eigenschaft einer Klasseninstanz einen Abschluss zuweisen und der Abschluss diese Instanz unter Bezugnahme auf die Instanz oder ihre Mitglieder erfasst, erstellen Sie einen starken Referenzzyklus zwischen dem Abschluss und der Instanz. Swift verwendet Erfassungslisten, um diese starken Referenzzyklen zu unterbrechen

ARC (Automatic Reference Counting)

Die Referenzzählung gilt nur für Instanzen von Klassen. Strukturen und Aufzählungen sind Werttypen, keine Referenztypen und werden nicht gespeichert und als Referenz übergeben.

Mohsen
quelle