¿Por qué restricciones constantes la propiedad de una instancia de estructura pero no la instancia de clase?

Cuando bash cambiar la propiedad ID de la instancia byValueObj , recibo un error que me byValueObj que no puedo asignar la propiedad de una constante, aunque la propiedad sea una variable. Sin embargo, puedo hacerlo en una instancia de clase. Como que sé que puede tener algo que ver con el valor y el mecanismo de referencia. Pero no tengo una comprensión muy clara y correcta de eso. ¿Alguien puede explicarlo por mí? Gracias.

 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 

Las estructuras en Swift son tipos de valores , y semánticamente hablando, los valores (es decir, ‘instancias’ de tipos de valores) son inmutables.

Una mutación de un tipo de valor, ya sea cambiando directamente el valor de una propiedad o mediante el uso de un método de mutating , equivale a asignarle un valor completamente nuevo a la variable que lo contiene (más los efectos secundarios desencadenados por la mutación). Por lo tanto, la variable que lo contiene debe ser una var . Y esta semántica está muy bien mostrada por el comportamiento de los observadores de propiedades en torno a los tipos de valores, como señala iGodric .

Entonces, lo que esto significa es que puedes pensar en esto:

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

al hacer esto:

 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) 

Y como puede ver claramente, este código es ilegal. No puede asignar fooCopy a foo , ya que es una constante de let . Por lo tanto, no puede cambiar la propiedad de un tipo de valor que se declara como let , y por lo tanto necesitaría convertirlo en var .

(Vale la pena señalar que el comstackdor en realidad no pasa por esta palabrería, sino que puede mutar las propiedades de las estructuras directamente, lo que se puede ver mirando el SIL generado . Sin embargo, esto no cambia la semántica de los tipos de valores).


La razón por la que puede cambiar una propiedad mutable de una instancia de clase constante let , se debe al hecho de que las clases son tipos de referencia. Por lo tanto, al ser constante, solo se garantiza que la referencia permanezca igual. La mutación de sus propiedades no afecta de ninguna manera su referencia a ellas; todavía se está refiriendo a la misma ubicación en la memoria.

Puede pensar en un tipo de referencia como un poste indicador, por lo tanto, escriba un código como este:

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

puedes pensar en la representación de la memoria así:

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

Y cuando mutes una propiedad:

 referenceToFoo.bar = 203 

La referencia ( referenceToFoo ) en sí misma no se ve afectada: aún está apuntando a la misma ubicación en la memoria. Es la propiedad de la instancia subyacente que ha cambiado (lo que significa que la instancia subyacente fue mutada):

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

Solo cuando intente asignar una nueva referencia a referenceToFoo , el comstackdor le dará un error, ya que está intentando mutar la referencia en sí:

 // 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() 

Por lo tanto, necesitaría hacer de referenceToFoo una var para legalizar esta asignación.

struct es un tipo de valor. Si los editas estás llamando al setter predeterminado para la propiedad que no es más que un método de mutación, que no es más que un método estático de la estructura que tiene self como primer argumento como inout que devuelve el método (Swift por ahora tiene curry syntax para las llamadas al método no aplicadas, pero lo cambiará a uno aplanado ). Solo como una nota al margen: cuando el método no está mutando, no se inout .

Como inout está trabajando por copiar y copiar , se llama a didSet , aunque no haya cambiado nada.

“Si pasa una propiedad que tiene observadores a una función como un parámetro de entrada y salida, los observadores willSet y didSet siempre se llaman”. Extracto de: Apple Inc. “El lenguaje de progtwigción Swift (Swift 3).” IBooks. https://itun.es/de/jEUH0.l

Código que una estructura de var se copiará cuando una propiedad está mutada:

 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 

Llama al didSet aunque solo didSet el Int en la propiedad de la estructura.

Si fuera una nueva copia completa, esperaría que la estructura mutada fuera una nueva copia con nueva asignación de memoria. Pero esto no es lo que sucede en el ejemplo, vea el código a continuación.

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

Entonces la estructura no se copia. Pero porque es inout :

“Escriba su código utilizando el modelo proporcionado por copiado copiado, sin depender de la optimización de llamada por referencia, para que se comporte correctamente con o sin la optimización.” Extracto de: Apple Inc. “El lenguaje de progtwigción Swift (Swift 3). “IBooks. https://itun.es/de/jEUH0.l


Ejemplo de inout solo con 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