值类型与引用类型:struct 与 class 的分工
1. 结论(先给答案)
struct(及多数enum)是值语义 :赋值/传参在模型上产生独立副本;class是引用语义:多个变量可指向同一实例。- 默认优先
struct,除非需要引用身份 、单继承 、运行时特性(deinit、@objc等) 、或与 UIKit/ObjC 对象模型 对齐,再选class。 class实例由 ARC 管理生命周期 ;struct本身不 走引用计数,但若struct含有class或闭包等引用类型字段,被包含的对象仍参与 ARC。- Swift 的类不支持 C++ 式多重继承 ;通过 协议多实现 + 协议默认实现 + 组合 达到类似「多能力」表达。
2. 深入浅出
- 直觉 :改
struct的副本一般不拖累另一处;改class的「一处」可能所有人看见。 - 机制 :
class实例在堆 上分配,变量持有引用 ;struct按值语义 传递,实现上可驻留在栈、寄存器、或作为更大对象的一部分 ,语义上始终是拷贝(大值可能经优化,但编程模型不变)。
2.1 struct 与 class 的使用场景(列举)
下列用于选型 checklist ;同一需求可能有多种写法,最终以团队规范 + 性能测量为准。
更适合 struct 的典型场景(尽量列全)
| 场景 | 说明 |
|---|---|
| 领域值模型 | 坐标、金额、时间区间、DTO(无「唯一身份」需求) |
| 不可变或 copy-on-write 友好数据 | 与 let、函数式风格配合 |
| 不需要继承 | 能力用 协议 + 扩展 组合,而非子类 |
SwiftUI View |
View 为 struct,由状态驱动重建 |
| 并发下减少共享可变引用 | 值拷贝缩小别名;仍注意「struct 内引用类型字段」的共享 |
| 与值语义强绑定的标准库风格 | 如自定义小集合、数学向量(无统一「身份」) |
| C / 固定布局互操作 | 有时需 struct 与 C 布局对齐(另涉 @frozen 等,进阶) |
更适合 class 的典型场景(尽量列全)
| 场景 | 说明 |
|---|---|
| 需要引用身份 | 用 === 判断「是不是同一个对象」;共享生命周期 |
| 需要单继承 | class Child: Parent;仅允许一个父类 |
需要 deinit |
释放非内存资源(句柄、取消观察者等);struct 无 deinit |
| 与 ObjC 互通 | 继承 NSObject、@objc、dynamic、KVO 等 |
| UIKit / AppKit 组件 | UIViewController、UIView 等均为 class |
| 共享可变状态且有意共享 | 如共享缓存、注册表(需配合线程安全策略) |
| 经典单例(需慎用) | 全局唯一实例,通常 class + 静态持有 |
明确不选某一侧的边界
| 需求 | 说明 |
|---|---|
| 多重继承(多个父类) | Swift 不支持;见 §2.3 |
| 仅要「多种能力」 | 用 多协议 + 协议扩展 ,或 组合(持有一个或多个服务对象) |
2.2 引用计数(ARC)与 class
- ARC(Automatic Reference Counting) 只作用于引用类型 的堆实例 (主要是
class;另有部分运行时对象如闭包捕获上下文等,细节见进阶资料)。 - 规则 :每多一个强引用 ,引用计数增加;强引用结束则减;为 0 时释放 对象。
weak不延长生命周期;unowned假定对象仍存活(不 bump 强引用,误用会野指针)。 struct与 ARC :struct整体 不按「实例」做 RC;若struct的存储属性里有class类型 或闭包 等,这些引用 会参与 ARC。复制struct会复制引用 (若属性是引用类型),从而可能多一个强引用 指向同一个堆对象。- 与 OC :OC 的 ARC 规则与 Swift 对 ObjC 对象 一致;纯 Swift
class同样走 ARC。
可核对:Automatic Reference Counting(Swift 官方)。
2.3 继承与「多继承」:Swift 的实际模型
- 类 :仅单继承 ------
class B: A只能有一个父类A。 - 协议 :可多采纳 ------
class C: A, P1, P2, P3中A为父类(至多一个),P1...均为协议;或struct S: P1, P2。 - 协议组合类型 :
P1 & P2(存在类型),用于「同时满足多个协议」。 - 默认实现 :
extension Proto { }提供协议方法默认实现,接近「混入」能力,但不是子类化多重继承。 - 与 OC 对照 :OC 同样单继承 + 多协议(
@protocol) + Category ;Swift 用 extension 更接近「非侵入式扩展」,语义与运行时注入与 Category 仍有差异。
结论 :面试或文档中说「Swift 没有多继承」通常指 没有多父类 ;若说「可以多协议」,应明确是 协议多实现 ,避免与 C++ 多重继承混淆。
2.4 内存分布(概念级,非实现承诺)
实现细节随 ABI、优化级别、平台变化;以下用于建立正确心智 ,不要依赖未文档化布局写代码。
| 对象 | 常见直觉(教学用) | 注意 |
|---|---|---|
class 实例 |
一般在堆上;头指针含类型信息与引用计数等元数据(具体布局见 Swift ABI) | 大小与对齐由编译器决定 |
小 struct |
常放在栈 或寄存器 ,或内联到父 struct/class 的存储中 |
大 struct 可能经临时缓冲等方式,仍不改变值语义 |
enum(无关联值或简单) |
常紧凑存储;有关联值时带 payload | 与 Optional 等优化相关 |
| 闭包 | 捕获列表可能形成堆上闭包对象,参与 ARC | 与「值类型」并列讨论 |
错误说法 :「struct 永远在栈上」------不准确;正确说法 :「struct 是值类型 ,按值语义使用;存放位置是实现细节。」
进阶阅读:Swift ABI / MemoryLayout(仅测大小对齐,不覆盖运行时堆块细节)。
3. Objective-C 对照
| OC | Swift | 为何不同 |
|---|---|---|
业务对象多为 NSObject 子类(引用) |
一等值类型 struct |
Swift 鼓励不可变与拷贝语义,减少共享可变状态 |
C struct 与 Swift struct 不等价 |
Swift struct 可有方法、协议 |
语言层统一值抽象 |
| 单继承 + 协议 + Category | 单继承 + 多协议 + extension | 无多父类;扩展机制不同 |
混编 :纯 Swift struct 对 OC 不可见 ;需 OC 侧使用时用 class + @objc 或桥接包装。
4. 为什么这么实现
- 值类型利于局部推理与并发友好(无共享可变引用时)。
- 引用类型保留身份 (
===)与单继承(与 UIKit/AppKit、ObjC 模型一致)。 - 禁止多父类 避免菱形继承等复杂度;能力用协议组合替代。
可核对:Structures and Classes(Swift 官方指南)。
5. 有没有更好的方式
- 全用
class:与 OC 一致但易共享可变状态;代价:更多循环引用与锁。 - 全用
struct:无法表达需要身份、单继承、deinit或 ObjC 子类化;代价:大值拷贝需 profiling。 - 协议 + 结构体 + 组合服务 :替代深继承树;代价:样板与 DI 设计成本。
6. 案例与反例
- 正例 :
Examples/SwiftSharing/Sources/FoundationExamples/main.swift---F01Demo;或Sources/ValueSemantics/main.swift---Point/Box。运行:swift run foundation-examples或swift run value-semantics。 - 反例 :
Array持有多个同一UIView引用,改一处影响多处------引用语义 ,非Array的锅。 - 反例 :假设「
struct里没有 class 就不会泄漏」------若struct内持有闭包且强捕获self,仍可能通过闭包间接持有class,需看引用图。
7. 坑与排雷
把「需要唯一身份」的模型做成 struct(例如需要 === 判同一对象)------应改为 class 或显式 ID。
误以为「值类型 = 无 ARC」------值类型内的引用仍参与 ARC。
8. 延伸阅读与自测
- Swift Book:Classes vs Structures;ARC 章节;Protocols。
- 自测:
MemoryLayout<Point>.size与class实例大小分别测什么? - 自测:为何 Swift 不允许多父类?
9. 面试常见问法
问:struct 和 class 区别?Swift 何时用 struct?
- 要点 :① 值 vs 引用;② 默认
struct;③ 身份 / 继承 /deinit/ ObjC / UIKit →class。 - 追问 :
Array存class时拷贝数组会不会拷贝对象?------不会,拷的是引用。 - 追问 :引用计数作用在谁上?------引用类型实例 ;
struct内嵌引用则嵌套对象参与 RC。 - 追问 :Swift 有多继承吗?------无多父类 ;可多协议,与协议扩展、组合。
- 追问 :
struct一定在栈上吗?------否 ,位置是实现细节;值语义才是保证。
符号 :FoundationExamplesMain、F01Demo、Point、Box --- Sources/FoundationExamples/main.swift;亦可对照 ValueSemantics target。