Swift中值类型与引用类型的深入剖析
立即解锁
发布时间: 2025-09-12 01:52:38 阅读量: 10 订阅数: 35 AIGC 


Swift编程实战精要
### Swift 中值类型与引用类型的深入剖析
#### 1. 值类型与引用类型的混合使用
在编程中,我们常常会思考能否将值类型置于引用类型内,或者将引用类型置于值类型内。答案是两者皆可。例如,在 `Company` 结构体中添加 `Employee` 类型的属性,就是将引用类型置于值类型内。不过,在值类型中使用引用类型时需格外谨慎,而在引用类型中使用值类型通常不会有特别的问题。
假设 `Acme Co.` 重组为 `Widget Co.`,在审核员工记录时,`Anika` 发现自己的员工编号实际上是 15 而非 16。以下代码展示了这一过程:
```swift
mel.id = 86
mel.id // 86
acme.boss.id // 16
let widgetCo = acme
anika.id = 15
widgetCo.boss.id // 15
```
当记录 `acme.boss.id` 的值时,结果为 16。接着,将 `acme` 的副本赋值给新常量 `widgetCo`。由于 `Company` 是值类型,本应期望 `widgetCo` 是 `acme` 的副本。随后,将 `anika` 的 ID 更新为 15。最后,检查 `widgetCo` 的老板 ID 时会发现其变为 15,这反映了 `anika` ID 的更改。这是因为 `boss` 属性是 `Employee` 类型,而 `Employee` 是类,属于引用类型。即便 `Company` 是值类型,其引用类型的属性依然只是引用。
这一示例表明,在值类型中放置引用类型会带来复杂性。通常,值类型的实例在赋值给新变量或常量,或者作为参数传递给函数时会被复制。但包含引用类型属性的值类型,会将对同一实例的引用传递给新变量或常量。通过任何一个常量或变量的属性对该实例所做的更改,都会在所有相关对象中体现。为避免混淆,一般应避免在值类型中使用引用类型属性。若确实需要在结构体中使用引用类型属性,最好使用不可变实例。
#### 2. 复制操作
复制的概念贯穿本章几乎所有主题。开发者常想了解复制实例时得到的是浅复制还是深复制。在 Swift 中,语言层面不支持深复制,因此复制操作均为浅复制。
以下示例有助于理解这些概念。假设 `Widget Co.` 招聘了一名新员工,创建一个新的 `Employee` 实例并将所有实例放入数组:
```swift
let juampa = Employee()
let employees = [anika, mel, juampa] // [{id 15}, {id 86}, {id 0}]
```
创建新员工 `juampa` 并将其与 `anika` 和 `mel` 加入新数组。在为公司派对邀请名单添加人员时,`Anika` 发现 `Juampa` 的 ID 录入有误。于是复制 `employees` 数组,更新 `juampa` 的 ID 并比较两个数组:
```swift
let juampa = Employee()
let employees = [anika, mel, juampa] // [{id 15}, {id 86}, {id 0}]
let partyGoers = employees // [{id 15}, {id 86}, {id 0}]
employees.last?.id = 4
employees // [{id 15}, {id 86}, {id 4}]
partyGoers // [{id 15}, {id 86}, {id 4}]
```
数组是结构体,属于值类型,本应期望 `partyGoers` 是 `employees` 的独立副本。但更改 `juampa` 的 ID 属性后,两个数组内容相同。这是因为 `employees` 数组包含 `Employee` 类型(引用类型)的实例,`partyGoers` 和 `employees` 数组引用的是相同的 `Employee` 实例,类似于 `acme` 和 `widgetCo` 共享对 `Employee` 的引用。
这种复制方式称为浅复制。浅复制不会遍历引用,值类型的浅复制复制值,引用类型的浅复制复制引用。而深复制会复制引用指向的实例,即 `partyGoers` 数组的元素不会引用与 `employees` 数组相同的 `Employee` 实例,而是创建一个新数组,其中的引用指向各自的 `Employee` 实例。Swift 未提供执行深复制的方法,若需要此功能,需自行实现。
#### 3. 相等性与同一性
理解值类型和引用类型的区别后,接下来探讨相等性和同一性。相等性指两个实例的可观察特征值相同,例如两个 `String` 类型的实例具有相同的文本。以下代码展示了相等性检查:
```swift
let x = 1
let y = 1
x == y // true
```
创建两个常量 `x` 和 `y`,它们均为 `Int` 类型且值相同,使用 `==` 进行相等性检查结果为 `true`。所有 Swift 的基本数据类型(如 `String`、`Int`、`Float`、`Double`、`Array`、`Set` 和 `Dictionary`)都可进行相等性检查。
同一性则指两个引用是否指向内存中的同一实例。由于 `Employee` 是引用类型,可使用同一性运算符 `===` 检查两个 `Employee` 实例的同一性:
```swift
acme.boss === anika // true
```
此同一性检查成功,并非因为 `Acme` 的老板与 `anika` 属性相同,而是因为 `Acme` 的老板就是 `anika`。再看以下代码:
```swift
let joe = Employee()
let sam = Employee()
joe === sam // false
```
此同一性检查失败,尽管 `joe` 和 `sam` 的员工 ID 均为 0,但它们并非指向同一实例。
若尝试对 `x` 和 `y` 进行同一性检查,使用 `x === y` 会导致编译器报错。因为 `===` 运算符用于检查引用类型变量所引用实例的内存地址,值类型变量不存储引用,所以同一性检查无意义。若尝试检查 `joe` 和 `sam` 的相等性,使用 `joe == sam` 也会引发编译器错误,因为编译器不知道如何在 `Employee` 类上调用 `==` 函数。若要对自定义类进行相等性检查,需实现 `==` 函数,这涉及遵循 `Equatable` 协议。
需要注意的是,两个常量或变量可能相等(即具有相同的值)但不同一(即可能指向给定类型的不同实例),但反之不成立:若两个变量或常量指向内存中的同一实例,则它们也相等。
#### 4. 如何选择
结构体和类都适合定义许多自定义类型。在 Swift 之前,在 macOS 和 iOS 开发中,结构体与类差异显著,两者的使用场景清晰。但在 Swift 中,结构体功能增强,行为与类更为相似,这使得选择使用哪种类型变得复杂。不过,结构体和类仍存在重要差异,可作为选择的参考:
- 若希望类型通过引用传递,应使用类。这样能确保该类型在赋值或作为函数参数传递时被引用而非复制。
- 若类型需要支持继承,则使用类,因为结构体不支持继承,不能被子类化。
- 否则,通常可选择结构体。
结构体常用于建模形状(如矩形有宽度和高度)、范围(如比赛有开始和结束)以及坐标系中的点(如二维空间中的点有 x 和 y 值),也适用于定义数据结构,Swift 标准库中的 `String`、`Array` 和 `Dictionary` 类型均定义为结构体。
在某些情况下,可能更倾向于使用类而非结构体,但这些情况相对较少,主要是需要引用语义或继承。例如,若想利用引用传递的优势,但又不希望类被子类化,可使用 `final` 类,标记为 `final` 的类不能被子类化,同时其实例可具有所需的引用语义。一般建议先使用结构体,除非确定需要引用类型的优势。值类型更易于理解,因为无需担心在副本上更改值时实例会发生什么。
#### 5. 写时复制(Copy on Write)
Swift 的复制行为可能会引发性能问题,例如每次将数组传递给函数或赋值
0
0
复制全文
相关推荐








