作为Swift中的另外一种自定义类型,从语法上来说,classstruct有很多相似的地方,例如:

struct PointValue {
    var x: Int
    var y: Int
}

class PointRef {
    var x: Int
    var y: Int
    
    init(x: Int, y: Int) {
        self.x = x
        self.y = y
    }
}

你可以看到,它们都可以用来自定义类型、都可以有properties,也都可以有methods。因此,单纯从语法上来理解class是个没什么意义的事情。在之前我们也说过,作为Swift中的引用类型,class表达的是一个具有明确生命周期的对象,我们需要关注这类内容的“生死存亡”,而值类型,我们更多关注的,就真的只是它的值而已。

接下来,作为这一章的开始,我们就通过一些例子,来感受下引用类型和值类型的差异。

引用类型必须明确指定init方法

首先,Swift并不会为class自动生成默认的init方法。如果我们不定义它,Swift编译器会报错。因此,无论多么简单的class,我们至少要为它定义一个初始化其所有属性的init方法。虽然有时候这样做很无聊,但是我们没有其它的选择。

为什么要如此呢?刚才我们说过了,class并不简单表达一个“值”的概念。Swift要求我们明确通过init方法说明“打造”一个对象的过程。相反,struct表达一个自定义的“值”,在没有特别说明的情况下,一个值的初始化当然是把它的每一个member都按顺序初始化。

引用类型关注的是对象本身

其次,classstruct对“常量”的理解是不同的。我们分别定义一个PointRefPointValue的常量:

let p1 = PointRef(x: 0, y: 0)
let p2 = PointValue(x: 0, y: 0)

同样是常量,当我们修改p2的属性时,编译器会报错:p2 is a let constant

p2.x = 10 // Compile time error

但是,我们却可以修改p1

p1.x = 10 // OK

这是因为,p2作为一个值类型,常量的意义当然是:“它的值不能被改变”。但是p1作为一个引用类型,常量的意义则变成了,它可以修改自身的属性,但不能再引用其他的PointRef对象。如果我们尝试让p1引用另外一个PointRef对象,就会发生下面的错误:

p1 = PointRef(x: 1, y: 1) // Compile time error

以上就是引用类型代表的“对象”和值类型代表的“值本身”在语义上的差别。而这种差别,还体现在了对它们各自进行赋值之后的表现上:

var p3 = p1
var p4 = p2

这之后,当我们使用===比较p1p3的时候,得到的结果是true

p1 === p3 // true

并且,当我们修改了p3之后,p1的值,会一并修改:

p3.x = 10
p1.x // 10

但是,当我们修改了一个值类型时,却并不会这样:

p4.x = 10
p2.x // 0

了解了引用和值在语义上的差别之后,我们继续来看这个差别在其各自的方法中,带来的差异。

引用类型默认是可以修改的

由于引用类型关注的是其引用的对象,而不是对象的值。因此,它的方法默认是可以修改对象属性的。例如:

class PointRef {
    // ...

    func move(to: PointRef) {
        self.x = to.x
        self.y = to.y
    }
}

但是,在之前我们讨论值类型的内容里,已经提到过了,对于PointValue来说move必须用mutating来修饰:

struct PointValue {
    // ...

    mutating func move(to: PointValue) {
        self.x = to.x
        self.y = to.y
    }
}

所以,修改一个struct的本意,实际上是你需要一个全新的值。

最后,还有一点要说明的是,在PointValue里,我们可以直接给self赋值:

mutating func move(to: PointValue) {
    self = to
}

编译器知道对一个值类型赋值就是简单的内存拷贝,因此,他会自动用to的每一个属性设置self的对应属性。但是,对于一个引用类型来说,你却不能这样:

class PointRef {
    // ...
    func move(to: PointRef) {
        self = to // !! Compile time error !!
    }
}

class的方法里,self自身是一个常量,我们不能直接让它引用其它的对象。

What's next?

以上就是我们这一节的内容。理解class这种类型要表达的语义,是可以正确使用它的前提。作为一个有明确生命周期的对象,创建它的init方法要比struct类型复杂的多。而理解这套略显复杂的规则,又是我们可以正确使用class类型的基础。在接下来的两个话题中,我们就深入其中,去了解可以用于class类型的init家族方法。

所有订阅均支持 12 期免息分期

¥ 59

按月订阅

一个月,观看并下载所有视频内容。初来泊学,这可能是个最好的开始。

开始订阅

¥ 512

按年订阅

一年的时间,让我们一起疯狂地狩猎知识吧。比按月订阅优惠 28%

开始订阅

¥ 1280

泊学终身会员

永久观看和下载所有泊学网站视频,并赠送 100 元商店优惠券。

我要加入
如需帮助,欢迎通过以下方式联系我们