本文实际上是《深入了解SwiftUI和Swift属性包装器的工作原理》一文的延续,延续并扩展了上一文中的属性观察器的知识。
在 Swift 中,didSet 是一个属性观察器,用于在属性的值发生变化后立即执行某些操作。它的触发条件与 Swift 属性的赋值过程密切相关。以下是详细的原理和解释:
属性赋值语法触发 didSet
属性观察器(didSet 和 willSet)是在属性的值被修改时触发的。这种修改必须通过显式的赋值语法完成,例如:
self.property = newValue
当对属性进行赋值操作时,Swift 会自动调用 didSet。这是因为属性赋值是 Swift 编译器内置的一个明确的触发点。
示例
var property: Int = 0 {
didSet {
print("Property changed from \(oldValue) to \(property)")
}
}
property = 10 // 打印:Property changed from 0 to 10
赋值语法 property = 10 明确改变了属性的值,因此触发了 didSet。
为什么只通过赋值语法触发?
这是因为 didSet 是在属性存储的上下文中实现的,而不是直接监听值的变化。以下是原因:
didSet 是属性存储的附加功能
属性存储是 Swift 编译器管理的核心功能。当属性的值通过赋值语法修改时,编译器会自动插入对 didSet 的调用。
如果值变化不是通过赋值语法完成(例如直接修改底层存储或通过其他机制更新值),didSet 不会被调用,因为这些操作绕过了 Swift 的属性存储逻辑。
内部优化
Swift 属性赋值语法(self.property = newValue)封装了对存储的访问和观察器调用。
如果允许其他形式的值变化(例如直接修改底层存储)触发 didSet,则会增加运行时的复杂性和性能开销。
确保一致性和可预测性
didSet 只触发一次,且只在显式赋值时触发,这让开发者更容易理解和预测代码行为。
其他操作不会触发 didSet 的原因
以下是一些常见情况下,didSet 不会触发的原因:
初始化
在属性初始化时,didSet 不会触发。比如:
var property: Int = 0 {
didSet {
print("Property changed")
}
}
let instance = MyClass() // 初始化时,didSet 不会触发
原因:
初始化是创建对象的过程,属性值还不存在,因此没有“旧值”可以比较。
直接修改底层存储
如果属性是通过某种方式直接操作底层存储,而不是通过赋值语法,didSet 不会触发。例如:
@propertyWrapper
struct MyWrapper {
var wrappedValue: Int {
get { return storage }
set { storage = newValue } // 这里绕过了 didSet 的触发
}
private var storage: Int
}
var property: Int = 0 {
didSet {
print("Property changed")
}
}
// 直接修改底层存储不会触发 didSet
属性观察器触发条件总结
会触发的情况
显式赋值:
self.property = newValue
通过 Binding 触发间接赋值(例如在 SwiftUI 中):
$property.wrappedValue = newValue
不会触发的情况
属性初始化:
var property = 10 // 不触发 didSet
直接修改底层存储:
_property.storage = newValue // 不触发 didSet
总结
didSet 的触发条件是通过显式的 属性赋值语法 (self.property = newValue) 修改值,因为:
1、Swift 属性观察器是与属性赋值逻辑绑定的。
2、只有赋值操作才能触发属性存储的变更逻辑,继而调用 didSet。
3、其他非赋值操作绕过了编译器对属性存储的管理,因此不会触发 didSet。