Swift 与 Objective-C

Swift 类和结构体的区别详解(值类型与引用类型)

使用 Swift 开发时,你总会遇到至少一次让自己停下来思考的时刻。

4 分钟阅读
Swift 类和结构体的区别详解(值类型与引用类型) 封面图

使用 Swift 开发时,你总会遇到至少一次让自己停下来思考的时刻。

“这个该用 class 还是 struct?”

刚开始学习 Swift 时,我也只是凭感觉在两者之间做选择。直到遇到奇怪的 bug,才真正认真研究它们的区别。

先说结论。

如果没有特殊理由,优先选择结构体(struct)。

只有在确实需要“共享的一份状态”时,才使用类。

Apple 的官方指南也持相同观点。今天我们逐一说明原因。


核心总结(3 点)

先为忙碌的读者总结重点。

  1. 结构体是值类型。复制后会变成完全不同的值。
  2. 类是引用类型。即使复制,仍然指向同一个实例。
  3. 如果不确定,先用 struct,只有需要继承或共享状态时才用 class。

记住这三句话,就完成一半了。


值类型和引用类型有什么区别?

只听文字说明确实容易困惑。直接看代码最快。

先看结构体。复制的瞬间,两者就完全独立了。

struct Point { var x = 0 }

var a = Point()
var b = a      // 值已复制
b.x = 10

print(a.x, b.x)
// 输出: 0 10

即使修改ba也保持不变,因为它们各自拥有自己的值。

接下来是类。代码看起来相似,但结果不同。

class Box { var x = 0 }

let a = Box()
let b = a      // 引用已复制(同一个对象)
b.x = 10

print(a.x, b.x)
// 输出: 10 10

明明只修改了ba也变成了 10。

因为两者指向同一个实例。这个曾经困扰我的 bug,原因正是如此。

复制还是共享?看箭头就能明白。
复制还是共享?看箭头就能明白。

那么什么时候该用哪一个?(选择标准)

在实际开发中做判断时,我会在脑中回想这张表。

情况 判断
只用于存放数据的模型 结构体
希望复制后彼此独立 结构体
必须使用继承
在多个地方共享同一个实例
需要与 Objective-C 互操作

再展开一点,可以总结为下面这样。

  • 如果要表示坐标、颜色、大小等值本身,使用结构体很自然。
  • 如果多个地方需要共同查看同一份状态,例如屏幕控制器或网络管理器,就应该使用类。
  • Apple 的 SwiftUI 也出于同样的原因将 View 定义为 struct:轻量且易于预测。

顺便一提,Swift 标准库中的ArrayStringDictionary全都是结构体。这说明值类型是多么基础。

我总是先问自己这个问题。
我总是先问自己这个问题。

为什么优先推荐结构体

值类型更容易预测。

不用担心传出的值在其他地方被悄悄修改。即使传给函数,原始值也很安全。

在多线程环境中也更安心。因为没有共享状态,数据竞争会减少。

相反,类虽然强大,却需要谨慎使用。如果同一个实例被多个地方持有,就很难追踪值在哪里发生了变化。

因此养成“默认使用 struct,需要时才用 class”的习惯,能大幅减少 bug。


什么时候使用,什么时候避免

最后,将判断标准简要总结如下。

  • 使用结构体:以数据为中心的模型、需要独立副本,以及重视线程安全时
  • 使用类:需要继承、必须共享一个实例,或标识(identity)具有意义时
  • 避免:习惯性地为简单模型使用 class,或明明需要共享却强行用 struct 绕开

面试时可以这样回答

问:结构体和类最大的区别是什么?

结构体是值类型,类是引用类型。复制结构体会得到完全独立的值,而复制类仍然指向同一个实例。此外,只有类支持继承并可以定义 deinit。

问:Swift 为什么推荐使用结构体?

因为值类型没有共享状态,副作用更少,也更容易预测。原始值不会被意外修改,在多线程环境中也更安全,有助于减少 bug。


一开始我也以为这只是语法差异,但越深入研究,越发现这是设计问题。

熟悉今天的判断标准后,从下一个项目开始就不会那么犹豫了。先问自己:是值,还是共享?

延伸阅读