Swift 值类型和引用类型

值和引用类型


值语义


1
2
3
4
var str = "Hello, playground"
var playgroundGreeting = str
playgroundGreeting += "How are you today?"
print(str) # "Hello, playground"

所以 str 的值并没有改变。因为字符串是一个结构体, 而结构体是值类型。那什么是值类型呢? 值类型在赋值给实例或作为参数传递给函数时总是被复制一份。

Swift 的基本类型 - Array, Dictionary, Int, String 等等都是用结构体来实现的, 它们都是值类型。所以在 Swift 中值类型是何等重要。 你应该在模型化你的数据时首先考虑结构体, 然后再考虑类。

引用语义


1
2
3
4
5
6
7
8
9
10
11
12
13
14
class GreekGod {
var name: String
init(name: String) {
self.name = name
}
}

let hecate = GreekGod(name: "Hecate") // 现在有了一个新的名为 "Hecate" 的 GreekGod 实例
let anotherHecate = hecate // 两个常量指向了同一个 GreekGod 类的实例

// 想想在 Perl 语言中的按值传递和按引用传递, 就知道引用能产生副作用
anotherHecate.name = "AnotherHecate"
anotherHecate.name
hecate.name // AnotherHecate

当你把类的实例赋值给一个常量或变量时, 那个常量或变量就拥有了那个实例的引用, 你也看到了, 引用和复制是不一样的。

img

常量值和引用类型


当值类型和引用类型是常量(使用 let 声明)的时候, 它俩的表现不一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 创建一个名为希腊众神的类
class GreekGod {
var name: String
init(name: String) {
self.name = name
}
}

let hecate = GreekGod(name: "Hecate") // 现在有了一个新的名为 "Hecate" 的 GreekGod 实例
let anotherHecate = hecate // 两个常量指向了同一个 GreekGod 类的实例

// 想想在 Perl 语言中的按值传递和按引用传递, 就知道引用能产生副作用
anotherHecate.name = "AnotherHecate"
anotherHecate.name
hecate.name // AnotherHecate

// 创建一个名为希腊神殿的结构体
struct Pantheon {
var chiefGod: GreekGod // 希腊诸神总是吵架, 所以让这个属性是可变的
}

// hecate: 赫卡特(司夜和冥界的女神)
let pantheon = Pantheon(chiefGod: hecate)
let zeus = GreekGod(name: "Zeus") // 宙斯
pantheon.chiefGod = zeus // 编译器在这儿会报错

编译器报错: Cannot assign toproperty: ‘pantheon’ is a ‘let’ constant。 这个错误告诉你 pantheon 是一个不可变的实例, 这意味着你改变不了它。 声明为常量(let)的值类型修改不了它们的属性, 即使这些属性在类型的实现中是用 var 声明的。 你可以把值类型的实例看作单个完整值的代表, 像一个整数那样。如果你把整数声明为一个常量, 那么之后你就不能修改了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

// 创建一个名为希腊众神的类
class GreekGod {
var name: String
init(name: String) {
self.name = name
}
}

let hecate = GreekGod(name: "Hecate")

let zeus = GreekGod(name: "Zeus") // 宙斯

zeus.name = "Zeus Jr"
zeus.name

看到了? 为什么我们不能改变值类型实例常量的属性值(例如 pantheon.chiefGod), 但是我们却能改变引用类型实例常量的属性值?(例如 zeus.name)

因为 zeus 是一个引用类型的实例, zeus 指向 GreekGod 的一个实例, 这个实例通过 GreekGod(name: "Zeus") 生成。当你改变 name 属性存储的值时, 你并没有真正改变 zeus , 它只是 GreekGod 的一个引用。因为你在定义 GreekGod 时让 name 是一个可变存储属性(使用 var), 那你就可以随心所欲修改这个属性的值了。无论你修改了多少次 zeus 的名字, zeus 仍旧指向同一个实例。

同时使用值类型和引用类型


这一章可能会让你疑惑, “我能把值类型放在引用类型里面吗?”; “我能把引用类型放在值类型里面吗?” 答案是可以。后者你可以通过在GreekGod 类中添加一个 Panteon 类型的属性。但是在值类型中使用引用类型你必须非常小心。