¿Es Swift Pass By Value o Pass By Reference?

100

Soy realmente nuevo en Swift y acabo de leer que las clases se pasan por referencia y que se copian las matrices / cadenas, etc.

¿Es el paso por referencia de la misma manera que en Objective-C o Java en el que realmente pasa "una" referencia o es el paso correcto por referencia?

gran_profaci
fuente
"¿Es el paso por referencia de la misma manera que en Objective-C o Java?" Ni Objective-C ni Java tienen paso por referencia.
newacct
2
Si. Yo sé eso. No pasas por referencia. Pasas la referencia por valor. Supuse que eso se sabía al contestar.
gran_profaci
Java pasa por valor, no por referencia.
6rchid

Respuestas:

167

Tipos de cosas en Swift

La regla es:

  • Las instancias de clase son tipos de referencia (es decir, su referencia a una instancia de clase es efectivamente un puntero )

  • Las funciones son tipos de referencia

  • Todo lo demás es un tipo de valor ; "todo lo demás" simplemente significa instancias de estructuras e instancias de enumeraciones, porque eso es todo lo que hay en Swift. Las matrices y cadenas son instancias de estructura, por ejemplo. Usted puede pasar una referencia a una de esas cosas (como un argumento de función) mediante el uso inouty tomando la dirección, como se ha señalado newacct. Pero el tipo es en sí mismo un tipo de valor.

Qué significan los tipos de referencia para usted

Un objeto de tipo de referencia es especial en la práctica porque:

  • La mera asignación o el paso a la función pueden producir múltiples referencias al mismo objeto

  • El objeto en sí es mutable incluso si la referencia a él es una constante ( letexplícita o implícita).

  • Una mutación en el objeto afecta a ese objeto tal como lo ven todas las referencias a él.

Esos pueden ser peligros, así que esté atento. Por otro lado, pasar un tipo de referencia es claramente eficiente porque solo se copia y pasa un puntero, lo cual es trivial.

Qué significan los tipos de valor para usted

Claramente, pasar un tipo de valor es "más seguro" y letsignifica lo que dice: no se puede mutar una instancia de estructura o una instancia de enumeración a través de una letreferencia. Por otro lado, esa seguridad se logra haciendo una copia separada del valor, ¿no es así? ¿No hace eso que pasar un tipo de valor sea potencialmente costoso?

Bueno, sí y no. No es tan malo como podría pensar. Como ha dicho Nate Cook, pasar un tipo de valor no implica necesariamente copiar, porque let(explícito o implícito) garantiza la inmutabilidad, por lo que no es necesario copiar nada. E incluso pasar a una varreferencia no significa que las cosas se copiarán, solo que pueden serlo si es necesario (porque hay una mutación). Los documentos le aconsejan específicamente que no se tuerza las bragas.

mate
fuente
6
"Las instancias de clase se pasan por referencia. Las funciones se pasan por referencia" Nop. Es paso por valor cuando el parámetro no es inoutindependiente del tipo. Si algo es paso por referencia es ortogonal a los tipos.
newacct
4
@newacct Bueno, por supuesto que tienes razón en un sentido estricto. Estrictamente, se debería decir que todo es paso por valor, pero que las instancias enum y las instancias de estructura son tipos de valor y que las instancias y funciones de clase son tipos de referencia . Ver, por ejemplo, developer.apple.com/swift/blog/?id=10 - Ver también developer.apple.com/library/ios/documentation/Swift/Conceptual/… Sin embargo, creo que lo que dije concuerda con la general sentido de las palabras significan.
Matt
6
Correcto, y los tipos de valor / tipos de referencia no deben confundirse con pasar por valor / pasar por referencia, porque los tipos de valor se pueden pasar por valor o por referencia, y los tipos de referencia también se pueden pasar por valor o por referencia.
newacct
1
@newacct discusión muy útil; Estoy reescribiendo mi resumen para que no engañe.
Matt
43

Siempre es paso por valor cuando el parámetro no lo es inout.

Siempre es de paso por referencia si el parámetro es inout. Sin embargo, esto es algo complicado por el hecho de que necesita usar explícitamente el &operador en el argumento cuando se pasa a un inoutparámetro, por lo que es posible que no se ajuste a la definición tradicional de paso por referencia, donde pasa la variable directamente.

newacct
fuente
3
Esta respuesta, combinada con la de Nate Cook, fue más clara para mí (proveniente de C ++) en cuanto al hecho de que incluso un "tipo de referencia" no se modificará fuera del alcance de la función a menos que lo especifique explícitamente (mediante el uso inout)
Gobe
10
inouten realidad no se pasa por referencia sino copia-entrada-salida. Sólo garantiza que después de la llamada a la función se asignará un valor modificado al argumento original. Parámetros de entrada-salida
Dalija Prasnikar
aunque es cierto que todo se pasa por valor. Las propiedades de los tipos de referencia se pueden modificar dentro de la función, ya que la copia hace referencia a la misma instancia.
MrAn3
42

Todo en Swift se pasa por "copia" por defecto, así que cuando pasas un tipo de valor obtienes una copia del valor, y cuando pasas un tipo de referencia obtienes una copia de la referencia, con todo lo que eso implica. (Es decir, la copia de la referencia aún apunta a la misma instancia que la referencia original).

Utilizo comillas de miedo alrededor de la "copia" de arriba porque Swift hace mucha optimización; siempre que sea posible, no se copia hasta que haya una mutación o la posibilidad de mutación. Dado que los parámetros son inmutables de forma predeterminada, esto significa que la mayoría de las veces no se realiza ninguna copia.

Nate Cook
fuente
Para mí, esta es la mejor respuesta, ya que aclaró que, por ejemplo, las propiedades de la instancia se pueden modificar dentro de la función, incluso si el parámetro es una copia (pasar por valor) porque apunta a la misma referencia.
MrAn3
9

Aquí hay una pequeña muestra de código para pasar por referencia. Evite hacer esto, a menos que tenga una razón sólida para hacerlo.

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

Llámalo así

var val1: String = "";
var val2: Int = -1;
ComputeSomeValues(&val1, &val2);
Chris Amelinckx
fuente
¿Por qué debería evitar hacer esto?
cerebro
1
@Brainless porque agrega complejidad innecesaria al código. Es mejor incorporar parámetros y devolver un único resultado. Tener que hacer esto, generalmente indica un diseño deficiente. Otra forma de decirlo es que los efectos secundarios ocultos en las variables referenciadas pasadas no son transparentes para la persona que llama.
Chris Amelinckx
Esto no pasa por referencia. inoutes un operador de copia de entrada y salida. Primero copiará en el objeto, luego sobrescribirá el objeto original después de que regrese la función. Si bien puede parecer lo mismo, existen diferencias sutiles.
Hannes Hertach
7

El blog Apple Swift Developer tiene una publicación llamada Value and Reference Types que proporciona una discusión clara y detallada sobre este mismo tema.

Citar:

Los tipos en Swift se dividen en una de dos categorías: primero, "tipos de valor", donde cada instancia mantiene una copia única de sus datos, generalmente definida como estructura, enumeración o tupla. El segundo, "tipos de referencia", donde las instancias comparten una única copia de los datos, y el tipo se define generalmente como una clase.

La publicación del blog Swift continúa explicando las diferencias con ejemplos y sugiere cuándo usaría uno sobre el otro.

blanco
fuente
1
Esto no responde a la pregunta. La pregunta es sobre el paso por valor frente al paso por referencia, que es completamente ortogonal a los tipos de valor frente a los tipos de referencia.
Jörg W Mittag
2

Las clases se pasan por referencias y otras se pasan por valor de forma predeterminada. Puede pasar por referencia utilizando la inoutpalabra clave.

Bahram Zangene
fuente
Esto es incorrecto. inoutes un operador de copia de entrada y salida. Primero copiará en el objeto, luego sobrescribirá el objeto original después de que regrese la función. Si bien puede parecer lo mismo, existen diferencias sutiles.
Hannes Hertach
2

Cuando usa inout con un operador infijo como + =, el símbolo de dirección & puede ignorarse. ¿Supongo que el compilador asume pasar por referencia?

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

origDictionary + = newDictionaryToAdd

Y muy bien, este diccionario 'agregar' solo escribe uno en la referencia original también, ¡tan genial para bloquear!

Jules Burt
fuente
2

Clases y estructuras

Una de las diferencias más importantes entre estructuras y clases es que las estructuras siempre se copian cuando se pasan en su código, pero las clases se pasan por referencia.

Cierres

Si asigna un cierre a una propiedad de una instancia de clase y el cierre captura esa instancia haciendo referencia a la instancia o sus miembros, creará un ciclo de referencia fuerte entre el cierre y la instancia. Swift usa listas de captura para romper estos fuertes ciclos de referencia

ARC (recuento automático de referencias)

El recuento de referencias se aplica solo a instancias de clases. Las estructuras y enumeraciones son tipos de valor, no tipos de referencia, y no se almacenan ni se pasan por referencia.

Mohsen
fuente