为什么从结构实例中不断的约束属性而不是类实例呢?

当我尝试更改byValueObj实例的ID属性时,我收到一个错误,告诉我我不能分配给一个常量的属性,即使该属性是一个variables。 但是,我可以在一个类实例上做到这一点。 我知道这可能与价值和借鉴机制有关。 但是我对此没有很清楚和正确的理解。 有人可以为我解释吗? 谢谢。

 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 

Swift中的结构是值types – 从语义上讲,值(即值types的“实例”)是不可变的。

值types的变化,通过直接改变属性的值,或者通过使用mutating方法,就等同于给variables持有一个全新的值(加上变异触发的任何副作用)。 因此,持有它的variables需要是一个var 。 正如iGodric指出的那样 ,这种语义很好地由属性观察者围绕价值types的行为展现出来 。

那么这意味着你可以想到这个:

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

像这样做:

 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) 

正如你可以清楚地看到的 – 这个代码是非法的。 你不能把fooCopy分配回fooCopy因为它是一个常量。 因此,您不能更改声明为let的值types的属性,因此需要将其设置为var

(值得注意的是编译器实际上并没有经过这个palaver;它可以直接改变结构的属性,这可以通过查看SIL生成 ,这并不会改变值types的语义)。


您可以更改let常量实例的可变属性的原因是因为类是引用types。 因此, let一个常数只能确保参考保持不变。 突变它们的属性并不会以任何方式影响你对它们的引用 – 你仍然指的是内存中的相同位置。

你可以想象一个引用types,像一个路标,因此代码是这样的:

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

你可以想像这样的内存表示:

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

而当你改变一个属性:

 referenceToFoo.bar = 203 

引用( referenceToFoo本身不受影响 – 您仍然指向内存中的相同位置。 这是底层实例的属性发生了变化(意味着底层实例发生了变化):

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

只有当你试图给referenceToFoo分配一个新的引用时,编译器会给你一个错误,因为你试图改变引用本身:

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

因此,您需要使referenceToFoo var变为合法。

struct是一个值types。 如果你编辑它们,你正在调用属性的默认设置器,这只是一个变异的方法,这不过是一个静态方法的结构,它有self作为第一个参数inout返回该方法(Swift现在有咖喱未应用方法调用的语法,但会将其更改为扁平化 )。 正如一个方面说明:当方法没有变异时,它不会inout

因为inout是通过拷贝进行拷贝的 ,即使没有任何变化, didSet也被调用。

“如果将具有观察者的属性作为inputparameter passing给函数,则始终会调用willSet和didSet观察者。”摘自:Apple Inc.“Swift编程语言(Swift 3)”iBooks。 https://itun.es/de/jEUH0.l

当一个属性发生变化时,将会复制一个var结构体的代码:

 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 

调用didSet即使我们只是在结构的属性中突变了Int。

如果它是完整的新副本,那么你会期望变异的结构是一个新的内存分配新副本。 但是这不是在这个例子中发生的,看下面的代码。

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

所以结构不被复制。 但是因为它是inout

“使用copy-in copy-out给出的模型编写你的代码,而不依赖于逐个引用的优化,这样在没有优化的情况下它的行为是正确的。”摘自:苹果公司“Swift编程语言(Swift 3)。“iBooks。 https://itun.es/de/jEUH0.l


只有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