swift - ¿Por qué las restricciones constantes de la propiedad de una instancia de estructura pero no la instancia de clase?
class structure (2)
Cuando trato de cambiar la propiedad de
ID
de la instancia
byValueObj
, recibí un error que me dijo que no puedo asignar la propiedad de una constante, aunque la propiedad es una variable.
Sin embargo, puedo hacerlo en una instancia de clase.
Como que sé que tal vez tenga algo que ver con el valor por y el mecanismo de referencia.
Pero no tengo una comprensión muy clara y correcta de ello.
¿Alguien me lo puede explicar?
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
, es equivalente a simplemente asignar un valor
completamente nuevo
a la variable que lo contiene (más cualquier efecto secundario que la mutación desencadene).
Por lo tanto, la variable que lo sostiene debe ser una
var
.
Y esta semántica se muestra muy bien 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
como haciendo 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
nuevo a
foo
, ya que es una constante
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 compilador en realidad no pasa por esta palabrería; puede mutar las propiedades de las estructuras directamente, lo que se puede ver al observar el SIL generado . Sin embargo, esto no cambia la semántica de los tipos de valor).
La razón por la que puede cambiar una propiedad mutable de una instancia de
clase
let
, se debe al hecho de que las clases son tipos de referencia.
Por lo tanto, ser una constante constante solo asegura 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 una señal, por lo tanto, codifique así:
class Foo {
var bar = 23
var baz = 59
}
// ...
let referenceToFoo = Foo()
Puedes pensar en la representación de la memoria de esta manera:
| referenceToFoo | ---> | Underlying Foo instance |
| (a reference to 0x2A) | |<----------------------->|
|0x2A |0x32 |0x3A
| bar: Int | baz : Int |
| 23 | 59 |
Y cuando mutas una propiedad:
referenceToFoo.bar = 203
La referencia (
referenceToFoo
) en
sí misma
no se ve afectada: todavía está apuntando a la misma ubicación en la memoria.
Es la propiedad de la instancia subyacente la 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 compilador 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
referenceToFoo
a
var
para que esta asignación sea legal.
struct
es un tipo de valor.
Si los edita, está llamando al configurador 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 sintaxis para llamadas a métodos no aplicados, pero la
cambiará a una aplanada
).
Solo como una nota al margen: cuando el método no está mutando, no será
inout
.
Debido a que
inout
está funcionando
copiando - copiando
, se llama a
didSet
, incluso si nada ha cambiado.
"Si pasa una propiedad que tiene observadores a una función como parámetro de entrada, los observadores willSet y didSet siempre se llaman". Extracto de: Apple Inc. "El lenguaje de programación Swift (Swift 3)". IBooks. https://itun.es/de/jEUH0.l
Código que se copiará una estructura
var
cuando se mute una propiedad:
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
un Int en la propiedad de la estructura.
Si fuera una copia nueva completa, esperaría que la estructura mutada fuera una copia nueva con una 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 la copia de copia de salida, 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 programación Swift (Swift 3) ". IBooks. https://itun.es/de/jEUH0.l
Ejemplo de código con solo
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