计算属性 (Computed Property) 和 存储属性 (Stored Property) 的区别?
在 Swift 中,存储属性 (Stored Property) 和 计算属性 (Computed Property) 是两种最基本的属性类型。它们的核心区别在于:一个是“存值”,一个是“算值”。
以下是详细的对比解析:
1. 核心概念区别
存储属性 (Stored Property)
- 定义:它是一个存储在特定类 (Class) 或结构体 (Structure) 实例里的常量 (
let) 或变量 (var)。 - 本质:它是内存中的容器,直接保存数据。
- 类比:就像一个盒子,里面放着具体的东西(比如一个数字、一个字符串)。
- 定义:它是一个存储在特定类 (Class) 或结构体 (Structure) 实例里的常量 (
计算属性 (Computed Property)
- 定义:它不直接存储值,而是提供一个 getter(获取器)和一个可选的 setter(设置器)来间接获取和设置其他属性或值。
- 本质:它是一段代码逻辑(函数),每次访问时实时计算结果。
- 类比:就像 Excel 表格里的公式(例如
=A1+B1),它本身不存数,而是根据别人的值算出来的。
2. 代码示例
让我们通过一个 Circle (圆) 的例子来看两者的区别:
plaintext
struct Circle {
// MARK: - 存储属性 (Stored Properties)
// 直接占用内存,保存半径的值
var radius: Double
// MARK: - 计算属性 (Computed Properties)
// 不占用存储圆的内存,每次调用时根据 radius 算出来
var diameter: Double {
get {
return radius * 2
}
set {
// 当设置直径时,自动反推并更新半径
radius = newValue / 2
}
}
// 只读计算属性 (只有 get)
var area: Double {
return Double.pi * radius * radius
}
}
var myCircle = Circle(radius: 5.0)
// 访问存储属性
print(myCircle.radius) // 输出: 5.0 (直接取值)
// 访问计算属性
print(myCircle.diameter) // 输出: 10.0 (执行 get 代码块: 5.0 * 2)
// 设置计算属性
myCircle.diameter = 20.0 // 执行 set 代码块: radius 变为 10.0
print(myCircle.radius) // 输出: 10.0
3. 详细对比表
| 特性 | 存储属性 (Stored) | 计算属性 (Computed) |
|---|---|---|
| 内存占用 | 占用内存,用于存储实际数据。 | 不占用存储数据的内存(只占用代码段内存)。 |
| 关键字 | 可以是 var (变量) 或 let (常量)。 |
必须是 var,因为计算结果可能随依赖项改变。 |
| 适用范围 | 类 (Class)、结构体 (Struct)。 | 类、结构体、枚举 (Enum)、扩展 (Extension)。 |
| 初始化 | 必须在构造过程完成前初始化(或有默认值)。 | 不需要初始化,因为它不存值。 |
| 属性观察器 | 支持 willSet 和 didSet。 |
不直接支持(在 setter 里直接写逻辑即可)。 |
| Lazy (懒加载) | 支持 lazy 修饰符(首次访问时才初始化)。 |
不支持 lazy(本身就是访问时才计算)。 |
4. 关键细节补充
A. 枚举 (Enum) 的限制
- 枚举不能有存储属性:因为枚举的内存结构是固定的(用于存储 case),不能动态增加额外的存储空间。
- 枚举可以有计算属性:因为计算属性本质是方法,不占实例内存。
plaintext
enum Direction {
case north, south
// 错误!枚举不能包含存储属性
// var speed: Int = 10
// 正确!计算属性
var description: String {
switch self {
case .north: return "Heading North"
case .south: return "Heading South"
}
}
}
B. 扩展 (Extension) 的限制
- 扩展不能添加存储属性:扩展不能改变原有类型的内存布局(不能往里面塞新数据)。
- 扩展可以添加计算属性:可以利用已有的数据计算出新逻辑。
C. 属性观察器 (Property Observers)
- 存储属性:你可以添加
willSet和didSet来监听值的变化。 - 计算属性:通常不需要观察器,因为你可以在
set方法内部直接编写逻辑。但如果你继承了一个父类的计算属性,你可以在子类中重写并添加观察器。
总结
- 如果你需要保存一个状态或数据,用 存储属性。
- 如果你需要根据其他数据动态算出一个值,或者需要转换数据,用 计算属性。