Swift 属性

Swift 中的属性(Properties)是与类型(结构体、类、枚举)关联的值,可以是存储属性计算属性类型属性。它们是 Swift 面向对象和协议编程的核心。


一、属性分类概览

类型说明适用
存储属性(Stored)实际存储值的变量/常量structclass
计算属性(Computed)通过计算得出,不存储值structclassenum
类型属性(Static / Class)属于类型本身所有类型
属性观察器监听属性变化存储属性
延迟属性(lazy)首次访问时初始化存储属性

1. 存储属性(Stored Properties)

struct Point {
    var x: Double = 0.0   // 可变存储属性
    let y: Double         // 常量存储属性
}

class Person {
    var name: String      // 实例存储属性
    static var species = "Homo sapiens"  // 类型存储属性
}
  • struct / enum:支持 var / let
  • class:只支持 var(即使是常量属性)

2. 计算属性(Computed Properties)

不存储值,通过 get / set 计算。

struct Circle {
    var radius: Double

    // 只读计算属性(可省略 get)
    var diameter: Double {
        return radius * 2
    }

    // 可读写计算属性
    var area: Double {
        get {
            return .pi * radius * radius
        }
        set {
            radius = sqrt(newValue / .pi)
        }
    }

    // 简写 setter
    var perimeter: Double {
        get { 2 * .pi * radius }
        set { radius = newValue / (2 * .pi) }
    }
}

var circle = Circle(radius: 5)
print(circle.area)        // 78.54...
circle.area = 154         // 自动更新 radius
print(circle.radius)      // ≈7.0

只读计算属性(推荐简写)

var description: String {
    "圆形,半径: \(radius)"
}

3. 类型属性(Type Properties)

使用 static(结构体/枚举)或 class(类中可重写)

struct Math {
    static let pi = 3.14159
    static var e = 2.71828
}

class AudioManager {
    static let shared = AudioManager()
    class var maxVolume: Int { 100 }  // 可被子类重写
}

print(Math.pi)           // 3.14159
print(AudioManager.shared)

static:不可重写
class:仅类中可用,可被子类重写


4. 属性观察器(Property Observers)

监听存储属性的变化

class StepCounter {
    var totalSteps: Int = 0 {
        willSet(newTotal) {
            print("即将设置为 \(newTotal)")
        }
        didSet {
            if totalSteps > oldValue {
                print("增加了 \(totalSteps - oldValue) 步")
            }
        }
    }
}

var counter = StepCounter()
counter.totalSteps = 100
// 即将设置为 100
// 增加了 100 步

counter.totalSteps = 150
// 增加了 50 步

不触发:

  • 初始化时
  • willSet / didSet 内部修改自身
  • 计算属性

5. 延迟存储属性(lazy)

首次访问时才初始化,适用于复杂或耗时对象。

class DataImporter {
    init() { print("正在导入数据...") }
    var data = ["Apple", "Banana"]
}

class DataManager {
    lazy var importer = DataImporter()
    var records: [String] = []
}

let manager = DataManager()
manager.records.append("User1")
// 此时 importer 未初始化
manager.importer.data  // 打印 "正在导入数据..."

注意事项

  • 必须是 var(不能是 let
  • 多线程访问需注意线程安全
  • 不能用于 letstatic / class 属性(除非结合 lazy static 技巧)

6. 属性包装器(Property Wrapper)Swift 5.1+

封装属性的存储和逻辑。

@propertyWrapper
struct Clamped<T: Comparable> {
    private var value: T
    private let range: ClosedRange<T>

    init(wrappedValue: T, range: ClosedRange<T>) {
        self.range = range
        self.value = clamped(wrappedValue, to: range)
    }

    var wrappedValue: T {
        get { value }
        set { value = clamped(newValue, to: range) }
    }

    private func clamped(_ value: T, to range: ClosedRange<T>) -> T {
        min(max(value, range.lowerBound), range.upperBound)
    }
}

struct VolumeControl {
    @Clamped(range: 0...100) var level: Int = 50
}

var control = VolumeControl()
control.level = 150
print(control.level)  // 100

control.level = -10
print(control.level)  // 0

内置属性包装器

包装器用途
@StateSwiftUI 状态管理
@PublishedCombine 发布变化
@Environment读取环境值
@ObservedObject观察对象
@AppStorageUserDefaults 持久化
import SwiftUI

struct ContentView: View {
    @State private var username = ""
    @AppStorage("hasOnboarded") var hasOnboarded = false

    var body: some View { ... }
}

7. 投影属性(Projected Value)

$ 访问包装器的“投影值”。

@propertyWrapper
struct NonEmpty {
    private var value: String = ""

    var wrappedValue: String {
        get { value }
        set { value = newValue.isEmpty ? "默认值" : newValue }
    }

    var projectedValue: Bool { !value.isEmpty }
}

struct User {
    @NonEmpty var name: String
}

var user = User()
print(user.$name)     // false
user.name = "Tom"
print(user.$name)     // true

8. 全局与局部属性

var globalCounter = 0  // 全局(延迟初始化)

func test() {
    var local = 1      // 局部
    lazy var computed = expensiveOperation()  // 错误!lazy 不能用于局部
}

9. 访问控制

class BankAccount {
    private(set) var balance: Double = 0  // 可读,不可写(外部)

    func deposit(_ amount: Double) {
        balance += amount
    }
}

最佳实践总结

场景推荐
简单数据存储属性(struct
派生值计算属性
单例 / 常量static let
延迟加载lazy var
值限制@propertyWrapper
变化通知didSet / @Published
配置项static var

常见面试题

  1. 计算属性会占用内存吗?
    → 不会,它是方法调用。
  2. lazystatic 能一起用吗?
    → 不能直接,但可用 static var _cache = [String: Any]() + 方法封装。
  3. 属性观察器在初始化时触发吗?
    → 不触发。
  4. 如何实现线程安全的 lazy 属性?
   private lazy var _data: [String] = {
       // 复杂初始化
       return []
   }() 
   // 配合 DispatchQueue 同步访问

小技巧

// 1. 自动合成 Equatable
struct User: Equatable {
    var id: Int
    var name: String
    // 自动 == 比较
}

// 2. 键路径动态访问
let keyPath = \VolumeControl.level
print(control[keyPath: keyPath])

// 3. @autoclosure 延迟执行
func log(_ message: @autoclosure () -> String) {
    #if DEBUG
    print(message())
    #endif
}

高级话题(可继续提问)

  • Copy-on-Write 优化(Array、String 等)
  • 属性包装器底层原理(_propertyName, $propertyName)
  • SwiftUI 中 @State vs @Binding vs @ObservedObject
  • 反射与 Mirror 遍历属性
  • KVC / KVO 在 Swift 中的替代方案

需要完整项目示例性能对比、或自定义属性包装器实战?欢迎继续提问!

文章已创建 2481

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部