È Swift Passa per valore o Passa per riferimento


100

Sono davvero nuovo di Swift e ho appena letto che le classi vengono passate per riferimento e gli array / stringhe ecc. Vengono copiati.

Il passaggio per riferimento è allo stesso modo di Objective-C o Java in cui si passa effettivamente un riferimento "a" o è corretto passare per riferimento?


"Il passaggio per riferimento è allo stesso modo di Objective-C o Java" Né Objective-C né Java hanno il passaggio per riferimento.
newacct

2
Sì. Lo so. Non passi per riferimento. Si passa il riferimento per valore. Ho pensato che fosse noto quando ho risposto.
gran_profaci

Java passa per valore, non per riferimento.
6rchid

Risposte:


167

Tipi di attività a swift

La regola è:

  • Le istanze di classe sono tipi di riferimento (ovvero il tuo riferimento a un'istanza di classe è effettivamente un puntatore )

  • Le funzioni sono tipi di riferimento

  • Tutto il resto è un tipo di valore ; "tutto il resto" significa semplicemente istanze di strutture e istanze di enumerazioni, perché questo è tutto ciò che c'è in Swift. Array e stringhe sono istanze di struct, ad esempio. È possibile passare un riferimento a una di quelle cose (come argomento di funzione) utilizzando inoute prendendo l'indirizzo, come newacct ha sottolineato. Ma il tipo è esso stesso un tipo di valore.

Cosa significano per te i tipi di riferimento

Un oggetto di tipo di riferimento è speciale in pratica perché:

  • La semplice assegnazione o il passaggio a una funzione può produrre più riferimenti allo stesso oggetto

  • L'oggetto stesso è mutabile anche se il riferimento ad esso è una costante ( let, esplicita o implicita).

  • Una mutazione dell'oggetto influenza quell'oggetto come visto da tutti i riferimenti ad esso.

Questi possono essere pericoli, quindi tieni gli occhi aperti. D'altra parte, il passaggio di un tipo di riferimento è chiaramente efficiente perché viene copiato e passato solo un puntatore, il che è banale.

Cosa significano i tipi di valore per te

Chiaramente, il passaggio di un tipo di valore è "più sicuro" e letsignifica ciò che dice: non è possibile modificare un'istanza di struct o enum tramite un letriferimento. D'altra parte, quella sicurezza si ottiene facendo una copia separata del valore, non è vero? Questo non rende il passaggio di un tipo di valore potenzialmente costoso?

Ebbene sì e no. Non è così male come potresti pensare. Come ha detto Nate Cook, il passaggio di un tipo di valore non implica necessariamente la copia, perché let(esplicito o implicito) garantisce l'immutabilità, quindi non è necessario copiare nulla. E anche il passaggio a una varreferenza non significa che le cose verranno copiate, solo che potranno esserlo se necessario (perché c'è una mutazione). I documenti ti consigliano specificamente di non mettere le mutande in una svolta.


6
"Le istanze di classe vengono passate per riferimento. Le funzioni vengono passate per riferimento" No. È un valore di passaggio quando il parametro non è inoutindipendentemente dal tipo. Se qualcosa è pass-by-reference è ortogonale ai tipi.
newacct

4
@newacct Beh, ovviamente hai ragione in senso stretto! In senso stretto, si dovrebbe dire che tutto è pass-by-value ma che le istanze di enum e le istanze di struct sono tipi di valore e che le istanze di classe e le funzioni sono tipi di riferimento . Vedi, ad esempio, developer.apple.com/swift/blog/?id=10 - Vedi anche developer.apple.com/library/ios/documentation/Swift/Conceptual/… Tuttavia, penso che quello che ho detto concordi con il generale senso delle parole significano.
matt

6
Giusto e i tipi di valore / tipi di riferimento non devono essere confusi con il valore di passaggio / il riferimento di passaggio, poiché i tipi di valore possono essere passati per valore o per riferimento e i tipi di riferimento possono anche essere passati per valore o per riferimento.
newacct

1
@newacct discussione molto utile; Sto riscrivendo il mio riassunto per timore che sia fuorviante.
matt

43

È sempre un valore di passaggio quando il parametro non lo è inout.

È sempre un riferimento passivo se il parametro è inout. Tuttavia, questo è un po 'complicato dal fatto che è necessario utilizzare esplicitamente l' &operatore sull'argomento quando si passa a un inoutparametro, quindi potrebbe non adattarsi alla definizione tradizionale di passaggio per riferimento, in cui si passa direttamente la variabile.


3
Questa risposta, combinata con quella di Nate Cook, è stata più chiara per me (proveniente da C ++) sul fatto che anche un "tipo di riferimento" non verrà modificato al di fuori dell'ambito della funzione a meno che tu non lo specifichi esplicitamente (usando inout)
Gobe

10
inoutin realtà non è passato per riferimento ma copy-in copy-out Garantisce solo che dopo la chiamata di funzione il valore modificato verrà assegnato all'argomento originale. Parametri in-out
Dalija Prasnikar

anche se è vero che tutto è passato per valore. Le proprietà dei tipi di riferimento possono essere modificate all'interno della funzione poiché la copia fa riferimento alla stessa istanza.
MrAn3

42

Tutto in Swift viene passato per "copia" per impostazione predefinita, quindi quando passi un tipo di valore ottieni una copia del valore, e quando passi un tipo di riferimento ottieni una copia del riferimento, con tutto ciò che ciò implica. (Cioè, la copia del riferimento punta ancora alla stessa istanza del riferimento originale.)

Uso virgolette spaventose intorno alla "copia" sopra perché Swift fa molta ottimizzazione; ove possibile, non copia fino a quando non c'è una mutazione o la possibilità di mutazione. Poiché i parametri sono immutabili per impostazione predefinita, ciò significa che la maggior parte delle volte non viene effettivamente eseguita alcuna copia.


Per me questa è la risposta migliore in quanto ha chiarito che ad esempio le proprietà dell'istanza possono essere modificate all'interno della funzione anche il parametro essendo una copia (passa per valore) perché punta allo stesso riferimento.
MrAn3

9

Ecco un piccolo esempio di codice da passare per riferimento. Evita di farlo, a meno che tu non abbia una buona ragione per farlo.

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

Chiamalo così

var val1: String = "";
var val2: Int = -1;
ComputeSomeValues(&val1, &val2);

Perché dovresti evitare di farlo?
Brainless

1
@Brainless perché aggiunge complessità non necessaria al codice. È meglio inserire i parametri e restituire un singolo risultato. Dover farlo, di solito segnala un design scadente. Un altro modo per dirlo è che gli effetti collaterali nascosti nelle variabili referenziate passate non sono trasparenti per il chiamante.
Chris Amelinckx

Questo non passa per riferimento. inoutè un operatore di copia in entrata e in uscita. Prima copierà nell'oggetto, quindi sovrascriverà l'oggetto originale al ritorno della funzione. Anche se può sembrare la stessa cosa, ci sono sottili differenze.
Hannes Hertach,

7

Il blog di Apple Swift Developer ha un post chiamato Value and Reference Types che fornisce una discussione chiara e dettagliata proprio su questo argomento.

Per citare:

I tipi in Swift rientrano in una delle due categorie: primo, "tipi di valore", in cui ogni istanza conserva una copia univoca dei propri dati, generalmente definita come struttura, enum o tupla. Il secondo, "tipi di riferimento", in cui le istanze condividono una singola copia dei dati e il tipo è generalmente definito come una classe.

Il post del blog Swift continua a spiegare le differenze con esempi e suggerisce quando utilizzeresti uno sull'altro.


1
Questo non risponde alla domanda. La domanda riguarda il passaggio per valore rispetto al passaggio per riferimento, che è completamente ortogonale ai tipi di valore rispetto ai tipi di riferimento.
Jörg W Mittag

2

Le classi vengono passate per riferimento e altre vengono passate per valore in modo predefinito. Puoi passare per riferimento utilizzando la inoutparola chiave.


Questo non è corretto. inoutè un operatore di copia in entrata e in uscita. Prima copierà nell'oggetto, quindi sovrascriverà l'oggetto originale al ritorno della funzione. Anche se può sembrare la stessa cosa, ci sono sottili differenze.
Hannes Hertach,

2

Quando usi inout con un operatore infisso come + =, il simbolo dell'indirizzo può essere ignorato. Immagino che il compilatore presuma il passaggio per riferimento?

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

origDictionary + = newDictionaryToAdd

E graziosamente questo dizionario 'add' fa solo una scrittura anche nel riferimento originale, quindi ottimo per il bloccaggio!


2

Classi e strutture

Una delle differenze più importanti tra strutture e classi è che le strutture vengono sempre copiate quando vengono passate nel codice, ma le classi vengono passate per riferimento.

Chiusure

Se si assegna una chiusura a una proprietà di un'istanza di classe e la chiusura cattura quell'istanza facendo riferimento all'istanza o ai suoi membri, si creerà un forte ciclo di riferimento tra la chiusura e l'istanza. Swift utilizza gli elenchi di acquisizione per interrompere questi forti cicli di riferimento

ARC (conteggio automatico dei riferimenti)

Il conteggio dei riferimenti si applica solo alle istanze delle classi. Le strutture e le enumerazioni sono tipi di valore, non tipi di riferimento e non vengono archiviate e passate per riferimento.

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.