willSet和didSet是Swift中的属性观察器(property observers),允许在属性值更改之前和之后执行代码。与computed properties不同,观察器不计算新值,只对更改做出反应。根据Apple文档The Swift Programming Language(2026),观察器在数据验证、界面同步和记录代码更改方面不可或缺。
要点
willSet和didSet是Swift中的property observers(属性观察器),是跟踪stored properties变化的内置机制。
与其他需要手动实现setter或回调系统的语言不同,Swift提供了声明式语法来响应变化。观察器直接在属性声明之后添加,不需要单独调用。
根据Apple Developer Documentation(2026),property observers支持任何类、结构或枚举的stored properties。它们不适用于computed properties,因为这些属性不存储值——对于它们,对变化的响应直接在setter中实现。
willSet在向属性分配新值之前立即调用。在willSet内部,可以访问包含将要设置的值的隐式参数newValue。此时,属性的当前值尚未更改——您可以直接通过属性读取旧值。
根据Swift Evolution proposal SE-0001(2024),willSet提供了在实际更改之前进行验证或记录的功能。如果在willSet中抛出异常——新值将不会被应用,这使得观察器成为数据保护机制。
didSet在分配新值之后立即调用。在didSet内部,可以访问包含更改前值的隐式参数oldValue。此时,属性已经包含新值,您可以将其与旧值进行比较。
根据Swift by Sundell(2025),didSet是最流行的观察器,因为它最常用于后处理:更新UI、重新计算依赖字段或在更改后将数据发送到服务器。
Property observers直接在属性之后用花括号声明。最小语法只需要一个观察器,但可以同时声明两个。
var score: Int = 0 {
willSet {
print("分数将更改为\(newValue)")
}
didSet {
print("分数已从\(oldValue)更改为\(score)")
}
}
两个观察器都是可选的——可以只指定willSet或只指定didSet。对于willSet,可以通过在括号中指定名称来重命名newValue。
var username: String = "guest" {
willSet(newName) {
print("即将设置\(newName)")
}
didSet(oldName) {
print("之前是\(oldName),现在是\(username)")
}
}
根据Swift Language Guide(2026),重命名参数可以提高代码的可读性,特别是当属性和观察器在具有长名称的大型项目中使用时。
执行顺序是严格确定的:首先willSet(旧值可用),然后赋值,然后didSet(新值可用)。
class Temperature {
var celsius: Double = 0.0 {
willSet {
print("温度将从\(celsius)更改为\(newValue)")
}
didSet {
if celsius > 100.0 {
print("超过沸点!")
}
}
}
}
重要限制:观察器在创建实例过程中初始化属性时不会被调用。在init内部,值赋值不会触发willSet和didSet——这可以防止在构造阶段出现不必要的副作用。
根据Apple Swift Blog(2025),这种行为与许多其他语言不同,在其他语言中,setter甚至在构造函数中被调用。Swift选择安全:观察器只在对象初始化完成后才开始工作。
在didSet内部更改属性可能导致观察器被重复调用。Swift不阻止递归——程序员必须自己控制。
var counter: Int = 0 {
didSet {
if counter < 5 {
counter += 1
}
}
}
这种代码如果没有退出条件,将造成完全递归并导致堆栈溢出。根据Stack Overflow Swift Community(2025),这是初学者在使用property observers时最常见的错误之一。
结构支持stored properties的willSet和didSet,没有限制。重要的是要记住,结构是值类型,在mutating方法内部修改属性也会触发观察器。
struct Point {
var x: Double = 0.0 {
didSet {
print("X已更改为\(x)")
}
}
var y: Double = 0.0 {
didSet {
print("Y已更改为\(y)")
}
}
}
var point = Point()
point.x = 5.0
属性观察器在Swift项目中用于广泛的任务:从UI同步到数据验证和记录。
didSet允许在设置无效值后立即撤销或更正它。这取代了Objective-C中冗长的setter,并确保模型级别的数据完整性。
var age: Int = 0 {
didSet {
if age < 0 || age > 150 {
age = oldValue
}
}
}
当相关属性更改时,可以自动更新界面元素,而无需单独调用更新方法。
var userName: String = "" {
didSet {
nameLabel.text = userName
}
}
willSet便于用于调试或审计目的的记录。您可以在应用之前记录时间和新值,确保日志包含用于分析的原始数据。
根据objc.io(2025),property observers在具有单向数据流的架构中特别有用,其中每个属性更改都被记录下来,以便后续重现状态。
Computed properties即时计算值并且没有存储,而willSet和didSet在具有实际值的stored property上工作。
| 特性 | Property Observers | Computed Properties |
|---|---|---|
| 存储值 | 是 | 否 |
| 在更改时执行代码 | 是 | 否 |
| 声明类型 | var | var(get/set) |
| 访问参数 | newValue、oldValue | newValue(在set中) |
| 初始化 | 需要初始值 | 不需要 |
关键区别:computed property在每次访问时计算值,而property observer对现有值的变化做出反应。它们之间的选择由语义决定——如果属性是从其他数据派生的,请使用computed;如果这是一个需要观察其更改的独立值,请使用willSet/didSet。
最常见的错误——在没有退出条件的情况下递归调用didSet。didSet内每次属性更改都会再次触发observer,导致无限循环。
第二个常见错误——尝试在let属性上使用观察器。Swift编译器会报错,因为let是常量。
第三个错误——忽略observer在初始化时不会被调用的事实。期望willSet在init内部触发的开发者会遇到意外的行为。
第四个问题——在extension中对属性应用观察器。Swift禁止在extension中为stored properties添加willSet/didSet。
根据Ray Wenderlich(2025),理解这些限制有助于在早期阶段避免错误,并使Swift代码更可预测。
常见问题
可以,每个观察器都是可选的。您可以只声明willSet、只声明didSet或同时声明两者。
不,Swift禁止在extension中为stored properties添加willSet和didSet。观察器只在类型的原始定义中声明。
不,在初始化时观察器不会被调用。这是一种保护机制,防止在对象构造阶段产生副作用。
willSet是在值更改之前执行的stored property观察器。Computed property中的setter是计算新值的方法,而不是观察现有值。
重复调用didSet将导致递归。没有退出条件,这将导致堆栈溢出和程序异常终止。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。