属性
swift中跟实例相关的属性可以分为两大类 存储属性和计算属性。
- 存储属性类似于成员变量,存储在实例的内存中,结构体,类可以定义存储属性,枚举不可以定义存储属性。
- 计算属性 本质是方法(函数),不占用实例的内存,枚举,结构体,类都可以定义计算属性。
追问:为啥枚举不能定义存储属性呢? 因为枚举中只用了存储case或者关联值的,所以肯定不能存一个存储属性来影响他的内存结构。
存储属性
存储属性(Stored Property)是存储在特定类或结构体实例中的常量或变量。
基本示例
struct Circle {
// 存储属性
var radius: Double
var diameter: Double
}
let circle = Circle(radius: 5, diameter: 10)
存储属性的特点
- 只能用于类和结构体
- 可以是变量存储属性(var)或常量存储属性(let)
- 可以在定义时设置默认值
- 可以在初始化器中设置初始值
计算属性
计算属性(Computed Property)不直接存储值,而是提供一个getter和可选的setter来间接获取和设置其他属性的值。
基本语法
var propertyName: Type {
get {
// 返回合适的值
}
set(newValue) {
// 设置相关的值
}
}
示例
struct Circle {
var radius: Double = 0
// 计算属性
var diameter: Double {
set {
radius = newValue / 2
}
get {
return radius * 2
}
}
}
var circle = Circle()
circle.radius = 5
print(circle.diameter) // 10.0
circle.diameter = 12
print(circle.radius) // 6.0
只读计算属性
struct Circle {
var radius: Double = 0
// 只读计算属性
var diameter: Double {
return radius * 2
}
// 简写形式
var area: Double {
radius * radius * 3.14
}
}
计算属性的特点
- 必须用
var声明,不能用let - 必须明确指定类型
- 可以用于类、结构体、枚举
- 不直接存储值,而是通过计算得出
属性观察器
属性观察器(Property Observer)用于监听和响应属性值的变化。 这个主要指的是非lazy的var存储属性
基本语法
var propertyName: Type = defaultValue {
willSet(newValue) {
// 在属性值被设置之前调用
}
didSet(oldValue) {
// 在属性值被设置之后调用
}
}
示例
struct Circle {
var radius: Double = 0 {
willSet {
print("radius 即将被设置为 \(newValue)")
}
didSet {
print("radius 已经从 \(oldValue) 改为 \(radius)")
}
}
}
var circle = Circle()
circle.radius = 10.5
// 输出:
// radius 即将被设置为 10.5
// radius 已经从 0.0 改为 10.5
属性观察器的特点
willSet:在属性值被设置之前调用,参数是将要设置的新值didSet:在属性值被设置之后调用,参数是设置前的旧值- 不能与计算属性同时使用
- 可以省略参数名,
willSet默认参数名为newValue,didSet默认参数名为oldValue
全局变量、局部变量
var num: Int = 10 {
willSet {
print("willSet", newValue)
}
didSet {
print("didSet", oldValue, num)
}
}
num = 11
// willSet 11
// didSet 10 11
inout的再次研究
inout参数的本质
struct Shape {
var width: Int
var side: Int {
willSet {
print("willSetSide", newValue)
}
didSet {
print("didSetSide", oldValue, side)
}
}
var girth: Int {
set {
width = newValue / side
print("setGirth", newValue)
}
get {
print("getGirth")
return width * side
}
}
func show() {
print("width=\(width), side=\(side)")
}
}
func test(_ num: inout Int) {
num = 20
}
var s = Shape(width: 10, side: 4)
test(&s.side)
s.show()
// willSetSide 20
// didSetSide 4 20
// width=10, side=20
inout的内存访问
func test(_ num1: inout Int, _ num2: inout Int) {
num1 = 20
num2 = 30
}
var s = Shape(width: 10, side: 4)
test(&s.side, &s.side) // 错误:同时访问同一内存地址
类型属性
类型属性(Type Property)是属于类型本身的属性,而不是属于类型的某个实例。
基本语法
// 存储类型属性
static var propertyName: Type = defaultValue
// 计算类型属性
static var propertyName: Type {
// 计算逻辑
}
// 类的计算类型属性(可被子类重写)
class var propertyName: Type {
// 计算逻辑
}
示例
struct Car {
static var count: Int = 0
static var totalCount: Int {
return count
}
init() {
Car.count += 1
}
}
let car1 = Car()
let car2 = Car()
print(Car.count) // 2
print(Car.totalCount) // 2
类型属性的特点
- 使用
static关键字定义 - 类的计算类型属性可以用
class关键字,允许子类重写 - 存储类型属性必须设置默认值
- 存储类型属性是延迟初始化的,线程安全
枚举类型属性
enum Season: CaseIterable {
case spring, summer, autumn, winter
static var count: Int {
return allCases.count
}
}
print(Season.count) // 4
懒加载属性
懒加载属性(Lazy Property)是指在第一次访问时才会计算其初始值的属性。
基本语法
lazy var propertyName: Type = {
// 初始化逻辑
return initialValue
}()
示例
class PhotoView {
lazy var image: UIImage = {
let url = "https://example.com/photo.jpg"
let data = Data(contentsOf: URL(string: url)!)
return UIImage(data: data!) ?? UIImage()
}()
}
let photoView = PhotoView()
// 此时 image 还没有被创建
let img = photoView.image
// 此时 image 才被创建和加载
懒加载属性的特点
- 必须用
var声明,不能用let - 必须有默认值或初始化表达式
- 只有在第一次访问时才会执行初始化代码
- 线程不安全,多线程同时访问可能导致多次初始化
- 可以减少内存占用,提高性能
懒加载属性与计算属性的区别
class DataManager {
// 懒加载属性:只计算一次
lazy var expensiveData: [String] = {
print("Computing expensive data...")
return ["data1", "data2", "data3"]
}()
// 计算属性:每次访问都计算
var computedData: [String] {
print("Computing data...")
return ["data1", "data2", "data3"]
}
}
let manager = DataManager()
print(manager.expensiveData) // 第一次访问,会打印 "Computing expensive data..."
print(manager.expensiveData) // 第二次访问,不会打印
print(manager.computedData) // 每次访问都会打印 "Computing data..."
print(manager.computedData)
懒加载属性的实际应用
class FileManager {
let filename: String
lazy var contents: String = {
print("Loading file: \(filename)")
return try! String(contentsOfFile: filename)
}()
init(filename: String) {
self.filename = filename
}
}
// 只有在真正需要文件内容时才会加载
let manager = FileManager(filename: "config.txt")
// 文件还没有被加载
let data = manager.contents
// 此时文件才被加载
总结
属性类型对比
| 属性类型 | 存储方式 | 何时计算 | 线程安全 | 使用场景 |
|---|---|---|---|---|
| 存储属性 | 直接存储 | 初始化时 | 是 | 简单数据存储 |
| 计算属性 | 不存储值 | 每次访问 | 取决于实现 | 依赖其他属性的值 |
| 懒加载属性 | 延迟存储 | 首次访问 | 否 | 昂贵的初始化操作 |
| 类型属性 | 类型级别 | 根据类型 | 存储类型属性是 | 类型级别的数据 |
最佳实践
- 存储属性:用于简单的数据存储,性能最好
- 计算属性:用于需要根据其他属性计算的值
- 懒加载属性:用于初始化成本高、可能不被使用的属性
- 属性观察器:用于监听属性变化,执行相关逻辑
- 类型属性:用于存储类型级别的数据或提供类型相关的计算
选择建议
- 优先使用存储属性,简单直接
- 需要计算的值使用计算属性
- 初始化成本高的使用懒加载属性
- 需要监听变化的使用属性观察器
- 类型级别的数据使用类型属性