É Swift Pass By Value ou Pass by Reference

100

Sou muito novo no Swift e acabei de ler que as classes são passadas por referência e os arrays / strings etc. são copiados.

A passagem por referência é da mesma maneira que em Objective-C ou Java em que você realmente passa "a" referência ou é passagem por referência adequada?

gran_profaci
fonte
"A passagem por referência é da mesma forma que em Objective-C ou Java?" Nem Objective-C nem Java tem passagem por referência.
newacct
2
Sim. Eu sei disso. Você não passa por referência. Você passa a referência por valor. Presumi que isso era sabido ao responder.
gran_profaci
Java passa por valor, não por referência.
6rchid

Respostas:

167

Tipos de coisas em Swift

A regra é:

  • Instâncias de classe são tipos de referência (ou seja, sua referência a uma instância de classe é efetivamente um ponteiro )

  • Funções são tipos de referência

  • Todo o resto é um tipo de valor ; "todo o resto" significa simplesmente instâncias de structs e instâncias de enums, porque isso é tudo que existe no Swift. Arrays e strings são instâncias de struct, por exemplo. Você pode passar uma referência a uma dessas coisas (como um argumento de função) usando inoute tomando o endereço, como newacct apontou. Mas o próprio tipo é um tipo de valor.

O que os tipos de referência significam para você

Um objeto de tipo de referência é especial na prática porque:

  • A mera atribuição ou passagem para uma função pode gerar várias referências ao mesmo objeto

  • O próprio objeto é mutável, mesmo se a referência a ele for uma constante ( letexplícita ou implícita).

  • Uma mutação no objeto afeta esse objeto como visto por todas as referências a ele.

Esses podem ser perigos, então fique de olho. Por outro lado, passar um tipo de referência é claramente eficiente porque apenas um ponteiro é copiado e passado, o que é trivial.

O que os tipos de valor significam para você

Claramente, passar um tipo de valor é "mais seguro" e letsignifica o que diz: você não pode transformar uma instância de struct ou de enum por meio de uma letreferência. Por outro lado, essa segurança é alcançada fazendo uma cópia separada do valor, não é? Isso não torna a passagem de um tipo de valor potencialmente cara?

Bem, sim e não. Não é tão ruim quanto você imagina. Como disse Nate Cook, passar um tipo de valor não implica necessariamente em copiar, porque let(explícito ou implícito) garante imutabilidade, então não há necessidade de copiar nada. E mesmo passar para uma varreferência não significa que as coisas serão copiadas, apenas que podem ser se necessário (porque há uma mutação). Os médicos aconselham especificamente que você não confunda suas calcinhas.

mate
fonte
6
"Instâncias de classe são passadas por referência. Funções são passadas por referência" Não. É passado por valor quando o parâmetro não é inoutindependente do tipo. Se algo é passado por referência é ortogonal aos tipos.
newacct
4
@newacct Bem, é claro que você está certo em sentido estrito! A rigor, deve-se dizer que tudo é passado por valor, mas que as instâncias de enum e as instâncias de struct são tipos de valor e que as instâncias e funções de classe são tipos de referência . Veja, por exemplo, developer.apple.com/swift/blog/?id=10 - Veja também developer.apple.com/library/ios/documentation/Swift/Conceptual/… No entanto, acho que o que eu disse está de acordo com o general sentido das palavras significam.
Matt
6
Certo, e tipos de valor / tipos de referência não devem ser confundidos com passagem por valor / passagem por referência, porque os tipos de valor podem ser transmitidos por valor ou por referência e os tipos de referência também podem ser transmitidos por valor ou por referência.
newacct
1
discussão muito útil @newacct; Estou reescrevendo meu resumo para que não me engane.
sábado
43

É sempre passado por valor quando o parâmetro não é inout.

É sempre passado por referência se o parâmetro for inout. No entanto, isso é um pouco complicado pelo fato de você precisar usar explicitamente o &operador no argumento ao passar para um inoutparâmetro, portanto, pode não se encaixar na definição tradicional de passagem por referência, em que você passa a variável diretamente.

newacct
fonte
3
Essa resposta, combinada com a de Nate Cook, foi mais clara para mim (vindo de C ++) em torno do fato de que mesmo um "tipo de referência" não será modificado fora do escopo da função, a menos que você especifique explicitamente (usando inout)
Gobe
10
inoutna verdade, não é passado por referência, mas copiado e copiado . Garante apenas que, após a chamada da função, o valor modificado será atribuído ao argumento original. Parâmetros de entrada
saída
embora seja verdade que tudo passa por valor. As propriedades dos tipos de referência podem ser modificadas dentro da função à medida que a cópia faz referência à mesma instância.
MrAn3
42

Tudo em Swift é passado por "cópia" por padrão, então quando você passa um tipo de valor, você obtém uma cópia do valor, e quando você passa um tipo de referência, você obtém uma cópia da referência, com tudo o que isso implica. (Ou seja, a cópia da referência ainda aponta para a mesma instância da referência original.)

Eu uso aspas em torno da "cópia" acima porque o Swift faz muitas otimizações; sempre que possível, ele não copia até que haja uma mutação ou a possibilidade de mutação. Como os parâmetros são imutáveis ​​por padrão, isso significa que na maioria das vezes nenhuma cópia realmente acontece.

Nate Cook
fonte
Para mim, esta é a melhor resposta, pois esclareceu que, por exemplo, as propriedades da instância podem ser modificadas dentro da função, mesmo o parâmetro sendo uma cópia (passagem por valor) porque aponta para a mesma referência.
MrAn3
9

Aqui está um pequeno exemplo de código para passar por referência. Evite fazer isso, a menos que tenha um forte motivo para isso.

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

Chame assim

var val1: String = "";
var val2: Int = -1;
ComputeSomeValues(&val1, &val2);
Chris Amelinckx
fonte
Por que você deve evitar fazer isso?
Brainless
1
@Brainless porque adiciona complexidade desnecessária ao código. É melhor inserir parâmetros e retornar um único resultado. Ter que fazer isso geralmente indica um design ruim. Outra forma de colocar isso é que os efeitos colaterais ocultos nas variáveis ​​referenciadas passadas não são transparentes para o chamador.
Chris Amelinckx
Isso não passa por referência. inouté um operador de cópia dentro e fora. Ele primeiro copiará no objeto e, em seguida, substituirá o objeto original após o retorno da função. Embora possa parecer o mesmo, existem diferenças sutis.
Hannes Hertach
7

O blog do desenvolvedor Apple Swift tem uma postagem chamada Tipos de valor e referência que fornece uma discussão clara e detalhada sobre esse tópico.

Citar:

Os tipos em Swift se enquadram em uma das duas categorias: primeiro, “tipos de valor”, onde cada instância mantém uma cópia exclusiva de seus dados, geralmente definida como uma estrutura, enum ou tupla. O segundo, “tipos de referência”, em que as instâncias compartilham uma única cópia dos dados, e o tipo é geralmente definido como uma classe.

A postagem do blog do Swift continua a explicar as diferenças com exemplos e sugere quando você deve usar um em vez do outro.

por quê?
fonte
1
Isso não responde à pergunta. A questão é sobre passagem por valor versus passagem por referência, que é completamente ortogonal aos tipos de valor versus tipos de referência.
Jörg W Mittag
2

As classes são passadas por referências e outras são passadas por valor padrão. Você pode passar por referência usando a inoutpalavra - chave.

Bahram Zangene
fonte
Isso está incorreto. inouté um operador de cópia dentro e fora. Ele primeiro copiará no objeto e, em seguida, substituirá o objeto original após o retorno da função. Embora possa parecer o mesmo, existem diferenças sutis.
Hannes Hertach
2

Quando você usa inout com um operador infixo, como + =, o símbolo de endereço & pode ser ignorado. Eu acho que o compilador assume passagem por referência?

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

origDictionary + = newDictionaryToAdd

E agradavelmente, este dicionário 'adiciona' só faz uma gravação na referência original também, tão bom para travar!

Jules Burt
fonte
2

Classes e estruturas

Uma das diferenças mais importantes entre estruturas e classes é que as estruturas são sempre copiadas quando são transmitidas em seu código, mas as classes são transmitidas por referência.

Fechamentos

Se você atribuir um encerramento a uma propriedade de uma instância de classe e o encerramento capturar essa instância referindo-se à instância ou a seus membros, você criará um forte ciclo de referência entre o encerramento e a instância. Swift usa listas de captura para quebrar esses fortes ciclos de referência

ARC (contagem automática de referência)

A contagem de referência se aplica apenas a instâncias de classes. Estruturas e enumerações são tipos de valor, não tipos de referência e não são armazenadas e passadas por referência.

Mohsen
fonte