Swift: Array als Referenz übergeben?

125

Ich will meinen Swift passieren Array account.chatszu chatsViewController.chatsdurch Verweis (so , dass , wenn ich einen Chat hinzufügen account.chats, chatsViewController.chatsnoch Punkte account.chats). Das heißt, ich möchte nicht, dass Swift die beiden Arrays trennt, wenn sich die Länge account.chatsändert.

ma11hew28
quelle
1
Am Ende habe ich nur accounteine globale Variable erstellt und die chatsEigenschaft von definiert ChatsViewControllerals : var chats: [Chat] { return account.chats }.
ma11hew28

Antworten:

72

Strukturen in Swift werden als Wert übergeben, aber Sie können den inoutModifikator verwenden, um Ihr Array zu ändern (siehe Antworten unten). Klassen werden als Referenz übergeben. Arrayund Dictionaryin Swift sind als Strukturen implementiert.

Kaan Dedeoglu
quelle
2
Das Array wird in Swift nicht kopiert / als Wert übergeben - es hat in Swift ein ganz anderes Verhalten als die reguläre Struktur. Siehe stackoverflow.com/questions/24450284/…
Boon
14
@Boon Array wird immer noch semantisch kopiert / als Wert übergeben, aber nur für die Verwendung von COW optimiert .
Eonil
4
Und ich empfehle nicht, von zu verwenden, NSArrayweil NSArrayund Swift-Array subtile semantische Unterschiede aufweisen (z. B. Referenztyp), und dies führt möglicherweise zu weiteren Fehlern.
Eonil
1
Das hat mich ernsthaft umgebracht. Ich schlug mir den Kopf, warum die Dinge nicht so funktionierten.
Khunshan
2
Was ist bei Verwendung inoutmit Structs?
Alston
137

Für den Funktionsparameteroperator verwenden wir:

let (es ist der Standardoperator, sodass wir let weglassen können ), um einen Parameter konstant zu machen (dies bedeutet, dass wir nicht einmal die lokale Kopie ändern können);

var , um es variabel zu machen (wir können es lokal ändern, aber es hat keinen Einfluss auf die externe Variable, die an die Funktion übergeben wurde); und

inout , um es zu einem In-Out-Parameter zu machen. In-out bedeutet in der Tat, Variablen als Referenz und nicht als Wert zu übergeben. Und es erfordert nicht nur, Wert als Referenz zu akzeptieren, sondern ihn auch als Referenz zu übergeben, also übergeben Sie ihn mit & - foo(&myVar)anstatt nurfoo(myVar)

Also mach es so:

var arr = [1, 2, 3]

func addItem(inout localArr: [Int]) {
    localArr.append(4)
}

addItem(&arr)    
println(arr) // it will print [1, 2, 3, 4]

Um genau zu sein, handelt es sich nicht nur um eine Referenz, sondern um einen echten Alias ​​für die externe Variable. Sie können also einen solchen Trick mit jedem Variablentyp ausführen, z. B. mit einer Ganzzahl (Sie können ihr einen neuen Wert zuweisen), obwohl dies möglicherweise kein Wert ist Gute Praxis und es kann verwirrend sein, die primitiven Datentypen wie folgt zu ändern.

Gene Karasev
quelle
11
Dies erklärt nicht wirklich, wie Array als Instanzvariable verwendet wird, auf die verwiesen wird und die nicht kopiert wird.
Matej Ukmar
2
Ich dachte, inout benutzte einen Getter und einen Setter, um das Array in ein temporäres zu kopieren und es dann auf dem Weg aus der Funktion zurückzusetzen - dh es kopiert
dumbledad
2
Tatsächlich verwendet In-Out Copy-In Copy-Out oder Call-by-Value-Ergebnis. Als Optimierung kann es jedoch als Referenz verwendet werden. "Als Optimierung wird, wenn das Argument ein Wert ist, der an einer physischen Adresse im Speicher gespeichert ist, derselbe Speicherort sowohl innerhalb als auch außerhalb des Funktionskörpers verwendet. Das optimierte Verhalten wird als Referenzaufruf bezeichnet; es erfüllt alle Anforderungen von das Copy-In-Copy-Out-Modell, während der Kopieraufwand entfällt. "
Tod Cunningham
11
In Swift 3 hat sich die inoutPosition geändert, dhfunc addItem(localArr: inout [Int])
elquimista
3
Ist varauch für das Funktionsparameterattribut nicht mehr verfügbar.
Elquimista
23

Definieren Sie sich eine BoxedArray<T>, die die ArraySchnittstelle implementiert , aber alle Funktionen an eine gespeicherte Eigenschaft delegiert. So wie

class BoxedArray<T> : MutableCollection, Reflectable, ... {
  var array : Array<T>

  // ...

  subscript (index: Int) -> T { 
    get { return array[index] }
    set(newValue) { array[index] = newValue }
  }
}

Verwenden Sie das BoxedArrayüberall dort, wo Sie ein verwenden möchten Array. Das Zuweisen eines BoxedArrayWillens erfolgt durch Referenz, es ist eine Klasse, und daher sind Änderungen an der gespeicherten Eigenschaft über die ArraySchnittstelle für alle Referenzen sichtbar.

GoZoner
quelle
Eine etwas beängstigende Lösung :) - nicht gerade elegant - aber es scheint, als würde es funktionieren.
Matej Ukmar
Nun, es ist sicher besser, als auf "Use NSArray" zurückzugreifen, um "Pass by Reference Semantics" zu erhalten!
GoZoner
13
Ich habe nur das Gefühl, Array als Struktur anstelle von Klasse zu definieren, ist ein Fehler beim Sprachdesign.
Matej Ukmar
Genau. Es gibt auch den Greuel, wo Stringein Subtyp von AnyABER ist, wenn Sie import Foundationdann Stringein Subtyp von werden AnyObject.
GoZoner
19

Verwenden Sie für die Swift-Versionen 3-4 (XCode 8-9)

var arr = [1, 2, 3]

func addItem(_ localArr: inout [Int]) {
    localArr.append(4)
}

addItem(&arr)
print(arr)
user6798251
quelle
3

Etwas wie

var a : Int[] = []
func test(inout b : Int[]) {
    b += [1,2,3,4,5]
}
test(&a)
println(a)

???

Christian Dietrich
quelle
3
Ich denke, die Frage ist nach einem Mittel, mit dem Eigenschaften von zwei verschiedenen Objekten auf dasselbe Array verweisen können. Wenn dies der Fall ist, ist Kaans Antwort richtig: Man muss entweder das Array in eine Klasse einschließen oder NSArray verwenden.
Wes Campaigne
1
Richtig, Inout funktioniert nur für die Lebensdauer des Funktionskörpers (kein
Christian Dietrich
Minor nit: es ist func test(b: inout [Int])... vielleicht ist dies eine alte Syntax; Ich bin erst 2016 zu Swift gekommen und diese Antwort stammt aus dem Jahr 2014. Vielleicht waren die Dinge früher anders?
Ray Toal
2

Eine andere Möglichkeit besteht darin, dass der Verbraucher des Arrays den Eigentümer nach Bedarf danach fragt. Zum Beispiel etwas in der Art von:

class Account {
    var chats : [String]!
    var chatsViewController : ChatsViewController!

    func InitViewController() {
        chatsViewController.getChats = { return self.chats }
    }

}

class ChatsViewController {
    var getChats: (() -> ([String]))!

    func doSomethingWithChats() {
        let chats = getChats()
        // use it as needed
    }
}

Sie können das Array dann innerhalb der Account-Klasse beliebig ändern. Beachten Sie, dass dies nicht hilfreich ist, wenn Sie das Array auch in der View Controller-Klasse ändern möchten.

elRobbo
quelle
0

Die Verwendung inoutist eine Lösung, die sich für mich jedoch nicht sehr schnell anfühlt, da Arrays Werttypen sind. Stilistisch bevorzuge ich persönlich die Rückgabe einer mutierten Kopie:

func doSomething(to arr: [Int]) -> [Int] {
    var arr = arr
    arr.append(3) // or likely some more complex operation
    return arr
}

var ids = [1, 2]
ids = doSomething(to: ids)
print(ids) // [1,2,3]
ToddH
quelle
1
Diese Art von Dingen hat einen Leistungsnachteil. Es wird ein bisschen weniger Telefonakku verwendet, um nur das ursprüngliche Array zu ändern :)
David Rector
Ich bin respektvoll anderer Meinung. In diesem Fall übertrifft die Lesbarkeit am Anrufort im Allgemeinen den Leistungseinbruch. Beginnen Sie mit unveränderlichem Code und optimieren Sie ihn, indem Sie ihn später veränderbar machen. Es gibt Fälle mit massiven Arrays, in denen Sie korrekt sind, aber in den meisten Apps ist dies ein Randfall von 0,01%.
ToddH
1
Ich weiß nicht, womit Sie nicht einverstanden sind. Das Kopieren des Arrays ist mit einer Leistungsminderung verbunden, und Operationen mit geringerer Leistung verbrauchen mehr CPU und daher mehr Batterie. Ich denke, Sie haben angenommen, dass ich gesagt habe, dass es eine bessere Lösung ist, was ich nicht war. Ich habe dafür gesorgt, dass die Leute alle Informationen zur Verfügung haben, um eine fundierte Entscheidung zu treffen.
David Rector
1
Ja, Sie haben Recht, es steht außer Frage, dass Inout effizienter ist. Ich dachte, Sie schlagen vor, dass dies inoutallgemein besser ist, weil es Batterie spart. Ich würde sagen, dass die Lesbarkeit, Unveränderlichkeit und Thread-Sicherheit dieser Lösung allgemein besser ist und dass inout nur in den seltenen Fällen als Optimierung verwendet werden sollte, in denen der Anwendungsfall dies rechtfertigt.
ToddH
0

Verwenden Sie a NSMutableArrayoder a NSArray, die Klassen sind

Auf diese Weise müssen Sie keinen Wraper implementieren und können den Build als Bridging verwenden

open class NSArray : NSObject, NSCopying, NSMutableCopying, NSSecureCoding, NSFastEnumeration
Peter Lapisu
quelle