Swift: ¿Pasar matriz por referencia?

125

Quiero pasar mi Swift Array account.chatsa chatsViewController.chatspor referencia (para que cuando agregue un chat account.chats, chatsViewController.chatstodavía apunte a account.chats). Es decir, no quiero que Swift separe las dos matrices cuando la duración de los account.chatscambios.

ma11hew28
fuente
1
Acabé haciendo accountuna variable global y definir la chatspropiedad de ChatsViewControllerque: var chats: [Chat] { return account.chats }.
ma11hew28

Respuestas:

72

Las estructuras en Swift se pasan por valor, pero puede usar el inoutmodificador para modificar su matriz (vea las respuestas a continuación). Las clases se pasan por referencia. Arrayy Dictionaryen Swift se implementan como estructuras.

Kaan Dedeoglu
fuente
2
La matriz no se copia / pasa por valor en Swift: tiene un comportamiento muy diferente en Swift en comparación con la estructura normal. Ver stackoverflow.com/questions/24450284/…
Boon
14
@Boon Array todavía se copia / pasa semánticamente por valor, pero solo está optimizado para usar COW .
eonil
44
Y no recomiendo usar NSArrayporque, NSArrayy la matriz Swift tiene diferencias semánticas sutiles (como el tipo de referencia), y eso posiblemente lo lleve a más errores.
eonil
1
Esto me estaba matando seriamente. Me estaba golpeando la cabeza por qué las cosas no funcionaban de la manera.
Khunshan el
2
¿Qué pasa si se usa inoutcon Structs?
Alston
137

Para el operador de parámetro de función usamos:

let (es el operador predeterminado, por lo que podemos omitir let ) para hacer que un parámetro sea constante (significa que no podemos modificar ni siquiera la copia local);

var para hacerlo variable (podemos modificarlo localmente, pero no afectará la variable externa que se ha pasado a la función); e

inout para convertirlo en un parámetro in-out. In-out significa, de hecho, pasar variables por referencia, no por valor. Y requiere no solo aceptar el valor por referencia, sino también pasarlo por referencia, así que páselo con & - en foo(&myVar)lugar de solofoo(myVar)

Entonces hazlo así:

var arr = [1, 2, 3]

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

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

Para ser exactos, no es solo una referencia, sino un alias real para la variable externa, por lo que puede hacer un truco con cualquier tipo de variable, por ejemplo, con un entero (puede asignarle un nuevo valor), aunque puede no ser un es una buena práctica y puede ser confuso modificar los tipos de datos primitivos como este.

Gene Karasev
fuente
11
Esto realmente no explica cómo usar la matriz como una variable de instancia a la que se hace referencia y no se copia.
Matej Ukmar
2
Pensé que inout usaba un getter y un setter para copiar la matriz a una temporal y luego restablecerla al salir de la función, es decir, se
copiaba
2
En realidad, in-out utiliza el resultado de copia en entrada o llamada por valor. Sin embargo, como optimización, puede usarse por referencia. "Como optimización, cuando el argumento es un valor almacenado en una dirección física en la memoria, se usa la misma ubicación de memoria tanto dentro como fuera del cuerpo de la función. El comportamiento optimizado se conoce como llamada por referencia; satisface todos los requisitos de el modelo de copia y copia mientras se elimina la sobrecarga de la copia ".
Tod Cunningham
11
En Swift 3, la inoutposición ha cambiado, es decirfunc addItem(localArr: inout [Int])
elquimista
3
Además, varya no está disponible para el atributo de parámetro de función.
elquimista
23

Defínese una BoxedArray<T>que implemente la Arrayinterfaz pero delegue todas las funciones a una propiedad almacenada. Como tal

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

  // ...

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

Use el BoxedArraylugar donde usaría un Array. La asignación de un BoxedArrayserá por referencia, es una clase y, por lo tanto, los cambios en la propiedad almacenada, a través de la Arrayinterfaz, serán visibles para todas las referencias.

GoZoner
fuente
Una solución un poco aterradora :), no exactamente elegante, pero parece que funcionaría.
Matej Ukmar
Bueno, seguro que es mejor que recurrir a 'Usar NSArray' para obtener 'pasar por semántica de referencia'.
GoZoner
13
Simplemente tengo la sensación de que definir Array como estructura en lugar de clase es un error de diseño del lenguaje.
Matej Ukmar
Estoy de acuerdo. También existe la abominación donde Stringhay un subtipo de AnyPERO si import Foundationluego te Stringconviertes en un subtipo de AnyObject.
GoZoner
19

Para las versiones Swift 3-4 (XCode 8-9), use

var arr = [1, 2, 3]

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

addItem(&arr)
print(arr)
usuario6798251
fuente
3

Algo como

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

???

Christian Dietrich
fuente
3
Creo que la pregunta es pedir un medio para que las propiedades de dos objetos diferentes apunten a la misma matriz. Si ese es el caso, la respuesta de Kaan es correcta: uno debe ajustar la matriz en una clase o usar NSArray.
Wes Campaigne
1
correcto, inout solo funciona durante la vida útil del cuerpo funcional (sin comportamiento de cierre)
Christian Dietrich
Minor nit: es func test(b: inout [Int])... tal vez esta es una sintaxis antigua; Solo entré en Swift en 2016 y esta respuesta es de 2014, ¿entonces quizás las cosas solían ser diferentes?
Ray Toal
2

Otra opción es hacer que el consumidor de la matriz se lo pida al propietario según sea necesario. Por ejemplo, algo en la línea de:

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

Luego puede modificar la matriz tanto como desee dentro de la clase Cuenta. Tenga en cuenta que esto no le ayuda si también desea modificar la matriz desde la clase de controlador de vista.

elRobbo
fuente
0

Usar inoutes una solución, pero no me parece muy rápido ya que las matrices son tipos de valor. Estilísticamente, personalmente prefiero devolver una copia mutada:

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
fuente
1
Hay un inconveniente en el rendimiento de este tipo de cosas. Utiliza un poco menos batería del teléfono para modificar la matriz original :)
David Rector
Respetuosamente no estoy de acuerdo. En este caso, la legibilidad en el sitio de la llamada generalmente supera el impacto en el rendimiento. Comience con un código inmutable y luego optimícelo haciéndolo mutable más tarde. Hay casos con matrices masivas en los que tienes razón, pero en la mayoría de las aplicaciones es un caso límite de 0.01%.
ToddH
1
No sé con qué estás en desacuerdo. Hay una penalización de rendimiento para copiar la matriz y las operaciones de menor rendimiento utilizan más CPU y, por lo tanto, más batería. Creo que asumiste que estaba diciendo que es una mejor solución, que no era. Me estaba asegurando de que las personas tengan toda la información disponible para tomar una decisión informada.
David Rector
1
Sí, tienes razón, no hay duda de que es más eficiente. Pensé que estabas sugiriendo que inoutes universalmente mejor porque ahorra batería. A lo que diría que la legibilidad, la inmutabilidad y la seguridad de subprocesos de esta solución son universalmente mejores y que inout solo debe usarse como una optimización en aquellos casos raros donde el caso de uso lo justifica.
ToddH
0

use a NSMutableArrayo a NSArray, que son clases

de esta manera no necesita implicar ningún envoltorio y puede usar la compilación en el puente

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