为什么常量约束属性来自结构实例而不是类实例?

时间:2016-06-24 10:13:21

标签: swift class structure constants

当我尝试更改ID实例的byValueObj属性时,我收到一条错误,告诉我无法分配常量的属性,即使该属性是变量。但是,我可以在类实例上执行此操作。我知道它可能与by值和引用机制有关。但我对它没有非常明确和正确的理解。有人可以帮我解释一下吗?感谢。

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

2 个答案:

答案 0 :(得分:13)

Swift中的结构是value types - 从语义上讲,值(即值类型的实例')是不可变的。

值类型的变异,无论是通过直接更改属性的值,还是通过使用mutating方法,都等同于仅将全新的值分配给持有它的变量(加上突变触发的任何副作用)。因此,持有它的变量需要是var。这个语义很好地通过属性观察者围绕值类型as iGodric points out的行为展示出来。

所以这意味着你可以想到这个:

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分配回foo - 因为它是let常量。因此,您无法更改声明为let的值类型的属性,因此需要将其设为var

(值得注意的是,编译器实际上没有通过这个方法;它可以直接改变结构的属性,这可以通过查看the SIL generated来看出。但这并没有改变值类型的语义。)

您可以更改let常量实例的可变属性的原因是由于类是引用类型。因此,let常量只能确保引用保持不变。改变他们的属性不会影响您对他们的引用 - 您仍然指的是内存中相同的位置。

您可以将引用类型视为路标,因此代码如下:

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     |

只有当您尝试将 new 引用分配给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才能使此作业合法化。

答案 1 :(得分:2)

struct是一种值类型。如果编辑它们,则调用属性的默认setter,它只是一个变异方法,它只是结构的静态方法,其self作为第一个参数inout返回方法(Swift现在有未应用的方法调用的curry语法,但是will change that to a flattened one)。正如旁注:当方法没有变异时,它不会是inout

由于inout正在使用复制 - 复制,因此即使没有任何更改,也会调用didSet

  

“如果将具有观察者的属性作为输入输出参数传递给函数,则始终调用willSet和didSet观察者。”   摘录自:Apple Inc.“The Swift Programming Language(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,即使我们只改变了struct的属性中的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提供的模型编写代码,而不依赖于逐个引用的优化,以便在优化或不优化的情况下它都能正常运行。”   摘录自:Apple Inc.“The Swift Programming Language(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