Swift: passa array por referência?

125

Quero passar meu Swift Array account.chatspara chatsViewController.chatsreferência (para que, quando eu adicionar um bate-papo account.chats, chatsViewController.chatsainda aponte para account.chats). Ou seja, não quero que o Swift separe as duas matrizes quando a duração das account.chatsalterações for alterada.

ma11hew28
fonte
1
I terminou apenas fazer accountuma variável global e definir a chatspropriedade de ChatsViewControllercomo: var chats: [Chat] { return account.chats }.
ma11hew28

Respostas:

72

As estruturas no Swift são passadas por valor, mas você pode usar o inoutmodificador para modificar sua matriz (veja as respostas abaixo). As aulas são passadas por referência. Arraye Dictionaryno Swift são implementados como estruturas.

Kaan Dedeoglu
fonte
2
A matriz não é copiada / passada por valor no Swift - ela tem um comportamento muito diferente no Swift em comparação com a estrutura regular. Veja stackoverflow.com/questions/24450284/…
Boon
14
O @Boon Array ainda é semanticamente copiado / passado por valor, mas apenas otimizado para usar COW .
eonil
4
E eu não recomendo o uso do NSArrayporque NSArraye o array Swift tem diferenças semânticas sutis (como o tipo de referência), e isso possivelmente leva a mais erros.
eonil
1
Isso estava me matando seriamente. Eu estava batendo minha cabeça por que as coisas não estão funcionando do jeito que está.
khunshan
2
E se usar inoutcom Structs?
Alston
137

Para o operador de parâmetro de função, usamos:

let (é o operador padrão, portanto, podemos omitir let ) para tornar um parâmetro constante (significa que não podemos modificar nem mesmo a cópia local);

var para torná-lo variável (podemos modificá-lo localmente, mas isso não afetará a variável externa que foi passada para a função); e

inout para torná-lo um parâmetro in-out. Na entrada significa na verdade passar variável por referência, não por valor. E requer não apenas aceitar valor por referência, mas também passá-lo por referência; assim, passe-o com & - em foo(&myVar)vez de apenasfoo(myVar)

Então faça assim:

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 exato, não é apenas uma referência, mas um alias real para a variável externa; portanto, você pode fazer esse truque com qualquer tipo de variável, por exemplo, com número inteiro (você pode atribuir um novo valor a ela), embora possa não ser um boa prática e pode ser confuso modificar os tipos de dados primitivos como este.

Gene Karasev
fonte
11
Isso realmente não explica como usar a matriz como uma variável de instância referenciada e não copiada.
Matej Ukmar 15/01
2
Eu pensei que inout usou um getter e um setter para copiar a matriz para uma temporária e, em seguida, redefini-la na saída da função - ou seja, cópias
dumbledad
2
Na verdade, a entrada e saída usa a entrada e saída por chamada ou resultado por valor. No entanto, como otimização, ele pode ser usado como referência. "Como uma otimização, quando o argumento é um valor armazenado em um endereço físico na memória, o mesmo local de memória é usado dentro e fora do corpo da função. O comportamento otimizado é conhecido como chamada por referência; atende a todos os requisitos de o modelo de entrada e saída enquanto remove a sobrecarga da cópia. "
Tod Cunningham
11
No Swift 3, a inoutposição mudou, iefunc addItem(localArr: inout [Int])
elquimista
3
Além disso, varnão está mais disponível para o atributo de parâmetro da função.
Elquimista
23

Defina você mesmo BoxedArray<T>que implementa a Arrayinterface, mas delega todas as funções para uma propriedade armazenada. Assim sendo

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

  // ...

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

Use o local BoxedArrayem que você usaria um Array. A atribuição de a BoxedArrayserá por referência, é uma classe e, portanto, as alterações na propriedade armazenada, através da Arrayinterface, serão visíveis para todas as referências.

GoZoner
fonte
Uma solução um pouco assustadora :) - não exatamente elegante - mas parece que funcionaria.
Matej Ukmar
Bem, com certeza é melhor do que voltar ao 'Use NSArray' para obter 'passar pela semântica de referência'!
GoZoner
13
Eu apenas sinto que definir Array como struct em vez de classe é um erro de design de linguagem.
Matej Ukmar
Concordo. Há também a abominação que Stringé um subtipo do AnyMAS se você import Foundation, em seguida, Stringtorna-se um subtipo de AnyObject.
GoZoner
19

Para as versões Swift 3-4 (XCode 8-9), use

var arr = [1, 2, 3]

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

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

Algo como

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

???

Christian Dietrich
fonte
3
Eu acho que a pergunta está pedindo um meio de ter propriedades de dois objetos diferentes apontar para a mesma matriz. Se for esse o caso, a resposta de Kaan está correta: é preciso agrupar a matriz em uma classe ou usar o NSArray.
Wes Campaigne
1
direita, inout só funciona para a vida do corpo da função (sem comportamento de encerramento)
Christian Dietrich
Nit menor: é func test(b: inout [Int])... talvez essa seja uma sintaxe antiga; Eu só entrei no Swift em 2016 e essa resposta é de 2014, então talvez as coisas fossem diferentes?
quer
2

Uma outra opção é fazer com que o consumidor da matriz peça ao proprietário conforme necessário. Por exemplo, algo como:

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

Você pode modificar a matriz o quanto quiser dentro da classe Account. Observe que isso não ajuda se você também deseja modificar a matriz da classe do controlador de exibição.

elRobbo
fonte
0

Usar inouté uma solução, mas não me parece muito rápida, pois matrizes são tipos de valor. Estilisticamente, eu pessoalmente prefiro retornar uma cópia 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
fonte
1
Há uma desvantagem de desempenho nesse tipo de coisa. Ele usa um pouquinho menos bateria do telefone para modificar apenas a matriz original :)
David Rector
Eu discordo respeitosamente. Nesse caso, a legibilidade no site da chamada geralmente supera o impacto no desempenho. Comece com código imutável e otimize, tornando-o mutável mais tarde. Existem casos com matrizes massivas em que você está correto, mas na maioria dos aplicativos esse é um caso de margem de 0,01%.
ToddH 14/06
1
Não sei do que você está discordando. Há uma penalidade de desempenho para copiar a matriz e operações de menor desempenho usam mais CPU e, portanto, mais bateria. Eu acho que você assumiu que eu estava dizendo que é uma solução melhor, o que eu não era. Eu estava garantindo que as pessoas tivessem todas as informações disponíveis para fazer uma escolha informada.
David Rector
1
Sim, você está correto, não há dúvida de que o inout é mais eficiente. Eu pensei que você estava sugerindo que inouté universalmente melhor porque economiza bateria. Para o qual eu diria que a legibilidade, a imutabilidade e a segurança de threads desta solução são universalmente melhores e que o inout deve ser usado apenas como uma otimização nos raros casos em que o caso de uso o justifica.
ToddH 16/06
0

use a NSMutableArrayou a NSArray, que são classes

Dessa forma, você não precisa implantar nenhum raspador e pode usar a construção em ponte

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