Swift中Struct(结构体)和 Class(类)的核心区别
在 Swift 中,Struct(结构体)和 Class(类)虽然在语法上看起来很像(都能定义属性、方法、下标、初始化器),但它们在底层逻辑和内存管理上有着根本的区别。
最核心的区别可以用一句话概括:Struct 是值类型(Value Type),而 Class 是引用类型(Reference Type)。
以下是详细的对比分析:
1. 核心区别:值类型 vs 引用类型
这是两者最大的分水岭。
Struct (值类型):
- 当你将一个结构体赋值给另一个变量,或者传递给函数时,系统会拷贝一份副本。
- 修改副本不会影响原始实例。
- 比喻:就像你给同事发了一份 Excel 文件。他在他的电脑上修改了数据,你电脑里的原文件不会变。
Class (引用类型):
- 当你将一个类实例赋值给另一个变量,或者传递给函数时,传递的是内存地址的引用(指针)。
- 修改新的变量会直接影响原始实例,因为它们指向内存中的同一个对象。
- 比喻:就像你给同事发了一个 Google Sheet(在线文档)的链接。他修改了数据,你打开链接看到的也是修改后的数据。
2. 内存管理 (Stack vs Heap)
Struct:
- 通常分配在栈(Stack)上(除非它是某个 Class 的属性)。
- 速度快:分配和释放非常高效,不需要复杂的内存管理开销。
- 线程安全:因为每个线程拿到的都是副本,所以天生具有较高的线程安全性。
Class:
- 总是分配在堆(Heap)上。
- 速度较慢:需要通过 ARC(自动引用计数)来管理内存。系统需要追踪有多少个变量指向这个对象,当计数为 0 时才销毁。
- 存在内存泄漏风险(如循环引用),需要小心使用
weak或unowned。
3. 继承 (Inheritance)
- Struct: 不支持继承。结构体不能继承另一个结构体。如果你需要复用代码,通常使用协议(Protocol)和扩展(Extension)。
- Class: 支持继承。一个类可以继承另一个类的属性和方法。
4. 初始化器 (Initializers)
- Struct: 编译器会自动赠送一个成员逐一初始化器 (Memberwise Initializer)。你不需要自己写
init方法。plaintextstruct Point { var x: Int; var y: Int } let p = Point(x: 10, y: 20) // 自动生成的 - Class: 除非所有属性都有默认值,否则你必须手动编写
init方法。
5. 可变性 (Mutability)
- Struct:
- 如果你把结构体实例赋给一个
let常量,那么它的所有属性(即使属性本身是var)都不可修改。 - 在结构体内部修改自身属性的方法,必须标记为
mutating。
- 如果你把结构体实例赋给一个
- Class:
- 即使你把类实例赋给一个
let常量,你依然可以修改它内部的var属性。因为let只是锁定了指针地址,没锁定指针指向的内容。
- 即使你把类实例赋给一个
6. 身份识别 (Identity)
- Struct: 只有“相等性”(Equality)。我们关心的是两个结构体的数据是否一样(使用
==,需遵守Equatable协议)。 - Class: 既有“相等性”,也有“同一性”(Identity)。我们可以判断两个变量是否指向内存中完全同一个对象(使用
===操作符)。
代码演示核心区别
plaintext
// MARK: - Struct (值类型)
struct HeroStruct {
var name: String
}
var hero1 = HeroStruct(name: "钢铁侠")
var hero2 = hero1 // 这里发生了拷贝,hero2 是一个新的副本
hero2.name = "蜘蛛侠"
print(hero1.name) // 输出: "钢铁侠" (原件未变)
print(hero2.name) // 输出: "蜘蛛侠"
// MARK: - Class (引用类型)
class HeroClass {
var name: String
init(name: String) { self.name = name }
}
var heroA = HeroClass(name: "钢铁侠")
var heroB = heroA // 这里传递的是引用(指针),指向同一个对象
heroB.name = "蜘蛛侠"
print(heroA.name) // 输出: "蜘蛛侠" (原件变了!)
print(heroB.name) // 输出: "蜘蛛侠"
总结:我该用哪一个?
Apple 官方的建议是 默认使用 Struct。
选择 Struct 的情况:
- 数据结构相对简单,主要目的是封装少量相关的数据(如坐标、尺寸、配置模型)。
- 你希望数据被拷贝,而不是被共享。
- 不需要继承。
- Swift 的标准库(String, Array, Dictionary, Int, Bool)全都是 Struct。
选择 Class 的情况:
- 你需要共享可变状态(例如:ViewModel,数据库管理器,网络连接器)。
- 你需要继承层级。
- 你需要控制对象的生命周期(使用
deinit)。 - 你需要与 Objective-C 代码互操作(Objective-C 的对象都是类)。