Swift值类型和引用类型

目录

引入

Swift中的类型分为两类: 值类型和引用类型。 这两种类型的行为方式各不相同, 了解它们的区别是理解Swift的重要部分。

想象一下, 你正在编写一份文档, 可能是一份报告或电子表格, 你希望朋友能看一看。 与朋友共享该文档有两种常见的方式: 通过通讯工具发送文档副本; 或者是线上编辑文档,直接分享文档链接。

通过这两种方式, 你的朋友都可以阅读并修改文档, 但两者存在不同。 如果是发送副本, 你的朋友将有一份完全独立的文档。 他可以随意修改内容, 但不会影响你的原始文档。 如果是发送链接, 你和你的朋友操作的将是同一份文档, 这意味着其中一个人修改, 也会影响到另一个人。

分享文档副本与链接的行为区别, 就像值类型和引用类型的区别一样。

值类型

在Swift中, 结构体、 枚举和元组都是值类型。 它们的行为类似于向朋友发送文档副本。 将值赋值给常量或变量, 或将值传递给函数或方法, 总是会生成该值的副本。

// 定义结构体
struct Document {
    var text: String
}

// 创建Document实例
var firstDoc = Document(text: "the first article")

// 将创建好的实例赋值给另一个变量
var lastDoc = firstDoc

// 修改第一个实例的text属性值
firstDoc.text = "what's that?"

print(firstDoc.text) //what's that?
print(lastDoc.text) // the first article,因为是副本,所以没有变化

因为会生成值的副本, 所以对于值类型, 你不必担心程序的其他部分会更改值进而影响到你。

引用类型

在Swift中, 类、Actor和闭包都是引用类型。 它们的行为类似于向朋友发送共享文档的链接。 将引用类型赋值给常量或变量, 或将其传递到函数或方法中,始终都是对被赋值或传递的共享实例的引用。

下面的代码与上面的示例完全相同, 但有一个很小但很重要的变化。 文档类型不再声明为结构体, 而是声明为类。 虽然代码变化不大, 但行为却发生了重大变化。

class Document {
    var text: String
    
    init(text: String) {
        self.text = text
    }
}

var myDoc = Document(text: "Great new article")
var friendDoc = myDoc

friendDoc.text = "Blah blah blah"

print(friendDoc.text) // prints "Blah blah blah"
print(myDoc.text) // prints "Blah blah blah"

对于引用类型, 程序中任何有引用的部分都可以修改。 所以有时意想不到的更改会导致错误。

局部推理

在上面代码示例中, 你可以逐行阅读代码, 了解同一引用是如何分配给两个不同变量的, 以及使用一个变量更改属性是如何更新两个变量引用实例的。 这种在一个点上阅读代码并弄清发生了什么的能力被称为局部推理能力。

现在想象一下, 在一个更大的程序中, 程序的不同部分都有对同一事物的引用。 你的代码可能会在某处设置一个值, 并依赖于该值, 但在其他地方, 你的程序中一个无关的部分可能会将该值从你手下改掉。

可从多个地方更改数据的正式名称是共享可变状态。 共享是因为它可以从代码中的多个地方访问, 可变是因为它可以改变, 又称突变, 而状态则是数据的同义词,就像 “事物的当前状态”。

在这种情况下, 如果不了解代码中许多不同地方可能发生的情况, 就无法完全理解代码中某一部分发生了什么。 您将失去进行局部推理的能力,这将使您的代码更难理解和调试。

使用值类型的一个优势是,你可以确定程序中没有其他地方会影响值。 您可以对眼前的代码进行推理, 而无需知道其他地方发生了什么。 这使您的代码更容易理解,并防止共享的可变状态发生意外或意外变化而产生错误。

选择值类型还是引用类型

对于你和你的朋友来说, 能够查看和编辑同一文档是非常有用的。 同样, 在程序中, 有时引用类型提供的共享可变状态也非常有用。

引用类型本质上并不坏, 但如上所述, 它们确实增加了额外的复杂性和出错的可能性。 一般来说, 最好使用结构体而不是类。 如果不需要引用类型的行为, 就没有必要承担增加的复杂性和陷阱。

组合值类型

代码中一种常见的设计模式是组合, 也就是把较小的元素组合在一起以创建较大的元素。 在Swift中, 你可以轻松地将值类型组合在一起,创建更复杂的值类型。

因此,你可以定义一个结构体, 其中包含一些基本类型, 如字符串、数字、布尔值或枚举值。 由于结构体中的所有内容都是值类型, 因此结构体的行为就像值类型一样。

您可能会有一个更复杂的结构类型, 它包含第一个结构的实例和一些其他值。 同样, 由于它是由值类型组成的, 所以这个结构也是一个值类型。

集合是值类型

在Swift中, 值类型的组成并不仅限于结构体和枚举。 虽然在许多语言中, 数组和字典等集合都是引用类型, 但在Swift中, 标准集合数组、字典和字符串都是值类型。

这意味着一个结构体可以包含结构体数组、 键值对字典、 枚举集。 只要一切都由值类型组成, 即使是复杂类型的实例也会被视为值。

结论

了解什么是值类型和引用类型, 以及它们在行为方式上的差异, 是学习Swift和推理代码的重要部分。 在这两者之间做出选择, 通常是选择将类型声明为结构还是类。下篇文章我们开始学习结构和类的语法细节。