5月27日 11:26

Swift 属性观察器怎么用?willSet 和 didSet 有什么区别?

属性观察器 willSet 和 didSet 在属性值变化时自动触发,让你在赋值前后插入自定义逻辑。willSet 在新值存储前调用,通过 newValue 访问即将写入的值;didSet 在新值存储后调用,通过 oldValue 访问替换前的值。

核心规则:willSet 拿到的是 newValue(还没写进去),didSet 拿到的是 oldValue(已经被替换掉),didSet 里直接读属性拿到的是新值。两个观察器不需要同时写,按需选择。

初始化器中设置属性值不会触发观察器——包括 init 里的赋值和设置默认值。这是因为对象还没构造完成,观察器依赖的状态可能不完整。延迟属性(lazy)和计算属性也不能有观察器——lazy 的初始化时机不确定,计算属性没有存储过程。

追问

willSet 和 didSet 的执行顺序是什么?

willSet 先执行,新值存储,然后 didSet 执行。在 willSet 里不能修改即将写入的值——newValue 是只读的。在 didSet 里可以修改属性值,但会再次触发 didSet,Swift 用了保护机制避免无限循环——didSet 里对自身的赋值不会再触发观察器。

didSet 里修改属性值有什么坑?

didSet 里给属性赋新值是合法的,常见做法是值校验后回退:if age < 0 { age = 0 }。但要注意,这个赋值不会再次触发 didSet(Swift 防止无限递归),所以你的回退逻辑只执行一次。另外,didSet 里的赋值会覆盖刚写入的值——如果你在 didSet 里又赋了别的值,外部看到的是 didSet 里赋的那个。

属性观察器和计算属性的 setter 有什么区别?

计算属性没有存储空间,setter 里必须自己决定怎么存(通常写到另一个私有属性里)。属性观察器附加在存储属性上,值的存储是自动的,你只是在存储前后加逻辑。选择标准:需要自定义存储方式用计算属性,只想在值变化时做额外操作用观察器。

父类和子类都有 didSet,执行顺序是什么?

子类 willSet → 父类 willSet → 新值存储 → 父类 didSet → 子类 didSet。和 init 不同,属性观察器在继承链中会逐级触发。如果子类重写了父类的属性并添加观察器,父类的观察器也会执行——即使父类不知道子类的存在。

什么场景下必须用 willSet 而不是 didSet?

willSet 的典型场景是"在新值写入前做校验或准备"。比如你需要在新值写入前记录日志(记录即将变成什么),或者需要根据新旧值的差异提前通知观察者。实际上大部分场景用 didSet 就够了——willSet 用得少得多。

写段代码

swift
class StepCounter { var totalSteps: Int = 0 { willSet { print("步骤将从 \(totalSteps) 变为 \(newValue)") } didSet { if totalSteps > oldValue { print("新增 \(totalSteps - oldValue) 步") } } } } let counter = StepCounter() counter.totalSteps = 100 // 触发 willSet + didSet counter.totalSteps = 150 // 触发 willSet + didSet // didSet 校验回退 class Person { var age: Int = 0 { didSet { if age < 0 { age = 0 } } } }
标签:Swift