8.属性

36 阅读6分钟

属性

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默认参数名为newValuedidSet默认参数名为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
// 此时文件才被加载

总结

属性类型对比

属性类型存储方式何时计算线程安全使用场景
存储属性直接存储初始化时简单数据存储
计算属性不存储值每次访问取决于实现依赖其他属性的值
懒加载属性延迟存储首次访问昂贵的初始化操作
类型属性类型级别根据类型存储类型属性是类型级别的数据

最佳实践

  1. 存储属性:用于简单的数据存储,性能最好
  2. 计算属性:用于需要根据其他属性计算的值
  3. 懒加载属性:用于初始化成本高、可能不被使用的属性
  4. 属性观察器:用于监听属性变化,执行相关逻辑
  5. 类型属性:用于存储类型级别的数据或提供类型相关的计算

选择建议

  • 优先使用存储属性,简单直接
  • 需要计算的值使用计算属性
  • 初始化成本高的使用懒加载属性
  • 需要监听变化的使用属性观察器
  • 类型级别的数据使用类型属性