Swift: passare l'array per riferimento?


126

Voglio passare la mia Swift Array account.chatsa chatsViewController.chatsper riferimento (in modo che quando aggiungo una chat per account.chats, chatsViewController.chatsancora punti a account.chats). Cioè, non voglio che Swift separi i due array quando la lunghezza delle account.chatsmodifiche.


1
Ho finito solo facendo accountuna variabile globale e definire la chatsproprietà di ChatsViewControllercome: var chats: [Chat] { return account.chats }.
ma11hew28

Risposte:


73

Le strutture in Swift vengono passate per valore, ma puoi utilizzare il inoutmodificatore per modificare l'array (vedi le risposte di seguito). Le classi vengono passate per riferimento. Arraye Dictionaryin Swift sono implementati come strutture.


2
L'array non viene copiato / passato per valore in Swift: ha un comportamento molto diverso in Swift rispetto alla struttura normale. Vedi stackoverflow.com/questions/24450284/…
Boon

14
@Boon Array è ancora semanticamente copiato / passato per valore, ma solo ottimizzato per uso COW .
eonil

4
E non consiglio di usare NSArrayperché NSArraye l'array Swift hanno sottili differenze semantiche (come il tipo di riferimento), e questo probabilmente ti porta a più bug.
eonil

1
Questo mi stava seriamente uccidendo. Stavo sbattendo la testa sul perché le cose non funzionassero in quel modo.
Khunshan

2
E se si utilizza inoutcon Structs?
Alston

137

Per la funzione parametro operator usiamo:

let (è l'operatore di default, quindi possiamo omettere let ) per rendere costante un parametro (significa che non possiamo modificare nemmeno la copia locale);

var per renderlo variabile (possiamo modificarlo localmente, ma non influenzerà la variabile esterna che è stata passata alla funzione); e

inout per renderlo un parametro in-out. In-out significa infatti passare la variabile per riferimento, non per valore. E richiede non solo di accettare il valore per riferimento, ma anche di passarlo per riferimento, quindi passalo con & - foo(&myVar)invece che solofoo(myVar)

Quindi fallo in questo modo:

var arr = [1, 2, 3]

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

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

Per essere esatti non è solo un riferimento, ma un vero alias per la variabile esterna, quindi puoi fare un trucco del genere con qualsiasi tipo di variabile, ad esempio con numero intero (puoi assegnargli un nuovo valore), anche se potrebbe non essere un buona pratica e potrebbe creare confusione modificare i tipi di dati primitivi come questo.


11
Questo in realtà non spiega come utilizzare array come variabile di istanza a cui si fa riferimento non copiata.
Matej Ukmar

2
Pensavo che inout usasse un getter e un setter per copiare l'array su un temporaneo e poi ripristinarlo all'uscita dalla funzione - cioè copia
dumbledad

2
In realtà in-out utilizza il copy-in copy-out o call by value result. Tuttavia, come ottimizzazione può essere utilizzato per riferimento. "Come ottimizzazione, quando l'argomento è un valore archiviato in un indirizzo fisico in memoria, la stessa posizione di memoria viene utilizzata sia all'interno che all'esterno del corpo della funzione. Il comportamento ottimizzato è noto come chiamata per riferimento; soddisfa tutti i requisiti di il modello di copia in copia e in uscita rimuovendo il sovraccarico di copia. "
Tod Cunningham

11
In Swift 3, la inoutposizione è cambiata, ovverofunc addItem(localArr: inout [Int])
elquimista

3
Inoltre, varnon è più disponibile per l'attributo del parametro della funzione.
elquimista

23

Definisci te stesso BoxedArray<T>che implementa l' Arrayinterfaccia ma delega tutte le funzioni a una proprietà memorizzata. Come tale

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

  // ...

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

Usa BoxedArrayovunque useresti un file Array. L'assegnazione di una BoxedArraysarà per riferimento, è una classe, e quindi le modifiche alla proprietà memorizzata, tramite l' Arrayinterfaccia, saranno visibili a tutti i riferimenti.


Una soluzione un po 'spaventosa :) - non esattamente elegante - ma sembra che funzionerebbe.
Matej Ukmar

Bene, sicuramente è meglio che ricadere su 'Usa NSArray' per ottenere 'passa per semantica di riferimento'!
GoZoner

13
Ho solo la sensazione che definire Array come struct invece di class sia un errore di progettazione del linguaggio.
Matej Ukmar

Sono d'accordo. C'è anche l'abominio dove Stringè un sottotipo di AnyMA se import Foundationpoi Stringdiventi un sottotipo di AnyObject.
GoZoner

19

Per le versioni 3-4 di Swift (XCode 8-9), usa

var arr = [1, 2, 3]

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

addItem(&arr)
print(arr)

3

Qualcosa di simile a

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

???


3
Penso che la domanda stia chiedendo un mezzo per avere proprietà di due oggetti diversi che puntano allo stesso array. In questo caso, la risposta di Kaan è corretta: si deve racchiudere l'array in una classe o utilizzare NSArray.
Wes Campaigne

1
giusto, inout funziona solo per la durata del corpo della funzione (nessun comportamento di chiusura)
Christian Dietrich

Minor nit: è func test(b: inout [Int])... forse questa è una vecchia sintassi; Sono entrato in Swift solo nel 2016 e questa risposta è del 2014, quindi forse le cose erano diverse?
Ray Toal

2

Un'altra opzione è che il consumatore dell'array lo chieda al proprietario secondo necessità. Ad esempio, qualcosa sulla falsariga di:

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
    }
}

Puoi quindi modificare l'array quanto vuoi all'interno della classe Account. Nota che questo non ti aiuta se vuoi modificare anche l'array dalla classe del controller di visualizzazione.


0

L'utilizzo inoutè una soluzione, ma non mi sembra molto veloce poiché gli array sono tipi di valore. Stilisticamente preferisco personalmente restituire una copia mutata:

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]

1
C'è uno svantaggio in termini di prestazioni in questo genere di cose. Utilizza un po 'meno la batteria del telefono per modificare semplicemente l'array originale :)
David Rector,

Rispettosamente non sono d'accordo. In questo caso la leggibilità sul sito della chiamata generalmente supera il calo delle prestazioni. Inizia con codice immutabile e quindi ottimizza rendendolo modificabile in un secondo momento. Ci sono casi con array enormi in cui sei corretto, ma nella maggior parte delle app è un caso limite dello 0,01%.
ToddH

1
Non so con cosa sei in disaccordo. Esiste una penalità nelle prestazioni per copiare l'array e le operazioni con prestazioni inferiori utilizzano più CPU e quindi più batteria. Penso che tu abbia pensato che stessi dicendo che è una soluzione migliore, ma non lo ero. Mi stavo assicurando che le persone avessero tutte le informazioni disponibili per fare una scelta informata.
David Rector,

1
Sì, hai ragione, non c'è dubbio che inout sia più efficiente. Pensavo stessi suggerendo che inoutè universalmente migliore perché fa risparmiare batteria. A cui direi che la leggibilità, l'immutabilità e la sicurezza dei thread di questa soluzione sono universalmente migliori e che inout dovrebbe essere utilizzato solo come ottimizzazione nei rari casi in cui il caso d'uso lo garantisce.
ToddH,

0

usa a NSMutableArrayo a NSArray, che sono classi

in questo modo non è necessario impiantare alcun wraper e puoi utilizzare la build in bridging

open class NSArray : NSObject, NSCopying, NSMutableCopying, NSSecureCoding, NSFastEnumeration
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.