Por que restrições constantes a propriedade de uma instância de estrutura, mas não a instância de class?

Ao tentar alterar a propriedade ID da instância byValueObj , recebi um erro que me disse que não posso atribuir à propriedade de uma constante, mesmo que a propriedade seja uma variável. No entanto, posso fazer isso em uma instância de class. Eu meio que sei que talvez tenha algo a ver com o valor por e pelo mecanismo de referência. Mas eu não tenho uma compreensão muito clara e correta disso. Alguém pode explicar isso para mim? Obrigado.

 struct CreatorValue{ var ID = 2201 } class CreatorRefer{ var ID = 2203 } let byValueObj = CreatorValue() let byReferObj = CreatorRefer() byValueObj.ID = 201 //Error: cannot assign to property: 'byValueObj' is a 'let' constant byReferObj.ID = 203 //works fine here 

Estruturas em Swift são tipos de valor – e, semanticamente, valores (ie ‘instâncias’ de tipos de valor) são imutáveis.

Uma mutação de um tipo de valor, seja alterando diretamente o valor de uma propriedade ou usando um método de mutating , é equivalente a apenas atribuir um valor completamente novo à variável que o contém (mais quaisquer efeitos colaterais que a mutação acionou). Portanto, a variável que a mantém precisa ser um var . E essa semântica é bem apresentada pelo comportamento de observadores de propriedade em torno de tipos de valor, como aponta iGodric .

Então, o que isso significa é que você pode pensar nisso:

 struct Foo { var bar = 23 var baz = 59 } // ... let foo = Foo() foo.bar = 7 // illegal 

como fazendo isso:

 let foo = Foo() var fooCopy = foo // temporary mutable copy of foo. fooCopy.bar = 7 // mutate one or more of the of the properties foo = fooCopy // re-assign back to the original (illegal as foo is declared as // a let constant) 

E como você pode ver claramente – esse código é ilegal. Você não pode atribuir fooCopy volta a foo – como é uma constante let . Portanto, não é possível alterar a propriedade de um tipo de valor declarado como let e, portanto, seria necessário torná-lo um var .

(Vale a pena notar que o compilador não passa por esse palaver; ele pode alterar as propriedades das estruturas diretamente, o que pode ser visto olhando para o SIL gerado . Isso não muda a semântica dos tipos de valor).


O motivo pelo qual você pode alterar uma propriedade mutável de uma instância de class de constante let é devido ao fato de que classs são tipos de referência. Portanto, ser uma constante de let apenas garante que a referência permaneça a mesma. Mutação de suas propriedades não afeta de forma alguma sua referência a elas – você ainda está se referindo ao mesmo local na memory.

Você pode pensar em um tipo de referência como um letreiro, portanto codifique como este:

 class Foo { var bar = 23 var baz = 59 } // ... let referenceToFoo = Foo() 

você pode pensar na representação da memory assim:

 | referenceToFoo | ---> | Underlying Foo instance | | (a reference to 0x2A) | |<----------------------->| |0x2A |0x32 |0x3A | bar: Int | baz : Int | | 23 | 59 | 

E quando você muta uma propriedade:

 referenceToFoo.bar = 203 

A referência ( referenceToFoo ) em si não é afetada – você ainda está apontando para o mesmo local na memory. É a propriedade da instância subjacente que foi alterada (o que significa que a instância subjacente sofreu uma mutação):

 | referenceToFoo | ---> | Underlying Foo instance | | (a reference to 0x2A) | |<----------------------->| |0x2A |0x32 |0x3A | bar: Int | baz : Int | | 203 | 59 | 

Somente quando você tentar atribuir uma nova referência a referenceToFoo , o compilador apresentará um erro, pois você está tentando alterar a própria referência:

 // attempt to assign a new reference to a new Foo instance to referenceToFoo. // will produce a compiler error, as referenceToFoo is declared as a let constant. referenceToFoo = Foo() 

Portanto, você precisaria fazer referenceToFoo a var para tornar essa atribuição legal.

struct é um tipo de valor. Se você editá-los, você está chamando o setter padrão para a propriedade que não é nada além de um método mutante, que não é senão um método estático da estrutura que tem self como primeiro argumento como inout que retorna o método (Swift por enquanto tem curry syntax para chamadas de método não aplicadas, mas mudará isso para uma chamada simplificada ). Apenas como uma nota lateral: Quando o método não está em mutação, não será inout .

Porque inout está trabalhando por copy-in-copy , didSet é chamado, mesmo que nada tenha mudado.

“Se você passar uma propriedade que tenha observadores para uma function como um parâmetro in-out, os observadores willSet e didSet serão sempre chamados.” Trecho de: Apple Inc. “A Swift Programming Language (Swift 3).” IBooks. https://itun.es/de/jEUH0.l

Codifique que uma var struct será copiada quando uma propriedade é modificada:

 struct Size { var width: Int var height: Int } struct Rectangle { var size: Size } var screenResolution = Rectangle.init(size: Size.init(width: 640, height: 480)) { didSet { print("Did set the var 'screenResolution' to a new value! New value is \(screenResolution)") } } screenResolution.size.width = 800 

Chama o didSet mesmo que nós apenas tenhamos mutado o Int na propriedade da estrutura.

Se fosse uma nova cópia completa, você esperaria que a estrutura mutada fosse uma nova cópia com nova alocação de memory. Mas isso não é o que acontece no exemplo, veja o código abaixo.

 // Value of a pointer is the address to the thing it points to internal func pointerValue(of pointer: UnsafeRawPointer) -> Int { return unsafeBitCast(pointer, to: Int.self) } internal struct MyStruct { internal var a: Int } internal var struct1: MyStruct = MyStruct.init(a: 1) pointerValue(of: &struct1) // output: 4405951104 struct1.a = 2 pointerValue(of: &struct1) // output: 4405951104 

Então a estrutura não é copiada. Mas porque é inout :

“Escreva seu código usando o modelo dado pela cópia copiada, sem depender da otimização de chamada por referência, para que ela se comporte corretamente com ou sem a otimização.” Trecho de: Apple Inc. “A linguagem de programação Swift (Swift 3). ”IBooks. https://itun.es/de/jEUH0.l


Exemplo de inout apenas com inout :

 struct MyType { var x: Int mutating func m1(y: Int) -> Int { x += 1 return x + y } } let mytypem1: (inout MyType) -> (Int) -> Int = MyType.m1 var myType = MyType.init(x: 1) // has to be "var" mytypem1(&myType)(2) // returns 3