Swift中的willSet和didSet:什么是属性观察器及其语法和工作原理

作者: IT Sectr 发布日期: 2026-06-20 阅读时间: 9 分钟

willSet和didSet是Swift中的属性观察器(property observers),允许在属性值更改之前和之后执行代码。与computed properties不同,观察器不计算新值,只对更改做出反应。根据Apple文档The Swift Programming Language(2026),观察器在数据验证、界面同步和记录代码更改方面不可或缺。

要点

  • willSet——在保存属性新值之前调用
  • didSet——在保存新值之后调用
  • newValue——willSet中的隐式参数,包含新值
  • oldValue——didSet中的隐式参数,包含旧值
  • 观察器——在初始化和init内部不起作用

Swift中的willSet和didSet是什么?

willSet和didSet是Swift中的property observers(属性观察器),是跟踪stored properties变化的内置机制。

与其他需要手动实现setter或回调系统的语言不同,Swift提供了声明式语法来响应变化。观察器直接在属性声明之后添加,不需要单独调用。

根据Apple Developer Documentation(2026),property observers支持任何类、结构或枚举的stored properties。它们不适用于computed properties,因为这些属性不存储值——对于它们,对变化的响应直接在setter中实现。

willSet何时被调用

willSet在向属性分配新值之前立即调用。在willSet内部,可以访问包含将要设置的值的隐式参数newValue。此时,属性的当前值尚未更改——您可以直接通过属性读取旧值。

根据Swift Evolution proposal SE-0001(2024),willSet提供了在实际更改之前进行验证或记录的功能。如果在willSet中抛出异常——新值将不会被应用,这使得观察器成为数据保护机制。

didSet何时被调用

didSet在分配新值之后立即调用。在didSet内部,可以访问包含更改前值的隐式参数oldValue。此时,属性已经包含新值,您可以将其与旧值进行比较。

根据Swift by Sundell(2025),didSet是最流行的观察器,因为它最常用于后处理:更新UI、重新计算依赖字段或在更改后将数据发送到服务器。

属性观察器的语法

Property observers直接在属性之后用花括号声明。最小语法只需要一个观察器,但可以同时声明两个。

swift
var score: Int = 0 {
    willSet {
        print("分数将更改为\(newValue)")
    }
    didSet {
        print("分数已从\(oldValue)更改为\(score)")
    }
}

两个观察器都是可选的——可以只指定willSet或只指定didSet。对于willSet,可以通过在括号中指定名称来重命名newValue。

swift
var username: String = "guest" {
    willSet(newName) {
        print("即将设置\(newName)")
    }
    didSet(oldName) {
        print("之前是\(oldName),现在是\(username)")
    }
}

根据Swift Language Guide(2026),重命名参数可以提高代码的可读性,特别是当属性和观察器在具有长名称的大型项目中使用时。

willSet和didSet如何工作

执行顺序是严格确定的:首先willSet(旧值可用),然后赋值,然后didSet(新值可用)。

swift
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不阻止递归——程序员必须自己控制。

swift
var counter: Int = 0 {
    didSet {
        if counter < 5 {
            counter += 1
        }
    }
}

这种代码如果没有退出条件,将造成完全递归并导致堆栈溢出。根据Stack Overflow Swift Community(2025),这是初学者在使用property observers时最常见的错误之一。

结构属性的观察器

结构支持stored properties的willSet和didSet,没有限制。重要的是要记住,结构是值类型,在mutating方法内部修改属性也会触发观察器。

swift
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,并确保模型级别的数据完整性。

swift
var age: Int = 0 {
    didSet {
        if age < 0 || age > 150 {
            age = oldValue
        }
    }
}

UI同步

相关属性更改时,可以自动更新界面元素,而无需单独调用更新方法。

swift
var userName: String = "" {
    didSet {
        nameLabel.text = userName
    }
}

更改记录

willSet便于用于调试或审计目的的记录。您可以在应用之前记录时间和新值,确保日志包含用于分析的原始数据。

根据objc.io(2025),property observers在具有单向数据流的架构中特别有用,其中每个属性更改都被记录下来,以便后续重现状态。

与computed properties的比较

Computed properties即时计算值并且没有存储,而willSet和didSet在具有实际值的stored property上工作。

特性Property ObserversComputed Properties
存储
在更改时执行代码
声明类型varvar(get/set)
访问参数newValue、oldValuenewValue(在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代码更可预测。

常见问题

可以在没有didSet的情况下使用willSet吗?

可以,每个观察器都是可选的。您可以只声明willSet、只声明didSet或同时声明两者。

观察器在extension中工作吗?

不,Swift禁止在extension中为stored properties添加willSet和didSet。观察器只在类型的原始定义中声明。

在init内部更改属性时会调用观察器吗?

不,初始化时观察器不会被调用。这是一种保护机制,防止在对象构造阶段产生副作用。

willSet与computed property中的setter有何不同?

willSet是在值更改之前执行的stored property观察器。Computed property中的setter是计算新值的方法,而不是观察现有值。

如果在didSet内部更改属性会发生什么?

重复调用didSet将导致递归。没有退出条件,这将导致堆栈溢出和程序异常终止。

总结

  • willSet——在属性更改之前执行的观察器,可访问newValue
  • didSet——在属性更改之后执行的观察器,可访问oldValue
  • 语法——observers在属性声明之后的花括号中声明
  • 初始化——observers在init内部和设置默认值时不起作用
  • 应用——验证、UI同步、记录、更改审计
  • 递归——在didSet内部更改属性需要显式的退出条件
  • Computed——不要与computed properties混淆:observers观察,computed计算

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读