首页 / Swift 编程语言教程 / 计算属性与属性观察器

Swift 编程语言教程

计算属性与属性观察器

本教程共 93 篇 · 第 53 篇 · 更新于 2026-08-08 · 约 8 分钟阅读

SwiftSwift 编程语言教程计算属性computed property属性观察器willSetdidSet

本节目标:读完你会写计算属性(get/set)、只读计算属性,以及用 willSet/didSet 监听属性变化。

1-1 存储属性不够用时

存储属性把值实实在在存下来。但有些值其实能由别的属性算出来,比如矩形的”中心点”由原点和尺寸决定。每次存一份既浪费又容易和源数据不一致——万一你改了 origin 却忘了同步 center,两份数据就对不上了。这时用计算属性。

计算属性的哲学是:能算出来的值,就不要存。它让”派生信息”始终和源数据保持一致,从根上消除了”两份数据打架”的隐患。

1-2 计算属性的 get/set

计算属性不存值,提供 getter 取值、可选 setter 设值:

struct Point {
    var x = 0.0, y = 0.0
}
struct Size {
    var width = 0.0, height = 0.0
}
struct Rect {
    var origin = Point()
    var size = Size()
    var center: Point {
        get {
            let cx = origin.x + size.width / 2
            let cy = origin.y + size.height / 2
            return Point(x: cx, y: cy)
        }
        set(newCenter) {
            origin.x = newCenter.x - size.width / 2
            origin.y = newCenter.y - size.height / 2
        }
    }
}

读 center 时跑 getter 现算;写 center 时跑 setter,反推回去改 origin。对外看起来和存储属性没两样。这种”读写都靠别的数据推导”的模式,让 center 永远是对的,你既可以从原点算中心,也可以设中心反推原点。

setter 若不给新值命名,默认叫 newValue,可省略括号:

set {
    origin.x = newValue.x - size.width / 2
    origin.y = newValue.y - size.height / 2
}

getter 若只有一句表达式,还能省略 return。这种简写在代码里很常见,写起来更紧凑。

1-3 只读计算属性

只有 getter、没有 setter 的是只读计算属性,访问但不能赋值。可省掉 get 关键字:

struct Cuboid {
    var width = 0.0, height = 0.0, depth = 0.0
    var volume: Double {
        return width * height * depth
    }
}

volume 由三维算出,设成可写反而含义不清,所以只读最合适。计算属性(含只读)必须用 var,因为它们的值不固定,会随其他属性变化。

1-4 属性观察器 willSet/didSet

想在某个属性”被赋值”时做点反应,用属性观察器。willSet 在存新值前调用,didSet 在存完后立即调用:

class StepCounter {
    var totalSteps: Int = 0 {
        willSet(newTotalSteps) {
            print("即将设为 \(newTotalSteps)")
        }
        didSet {
            if totalSteps > oldValue {
                print("增加了 \(totalSteps - oldValue) 步")
            }
        }
    }
}
let sc = StepCounter()
sc.totalSteps = 200
sc.totalSteps = 360

willSet 默认参数叫 newValue,didSet 默认叫 oldValue。即使新值和当前值相同,观察器也会触发。

Warning

计算属性想监听变化,别用 willSet/didSet,直接在它的 setter 里写逻辑即可。观察器只用于”你定义的存储属性”和”继承来的属性”。把观察器挂到计算属性上是编译不过的。

1-5 观察器的典型用途

观察器适合做约束与联动。比如把输入限制在阈值内,或同步更新别的状态。配合类型属性还能做全局统计,如记录所有实例达到过的最高值。一个常见坑是:在 didSet 里给属性赋新值,不会再次触发观察器,所以不会陷入无限循环——这点可以放心利用。

1-6 计算属性与观察器怎么选

简单记:想要”值由别的数据推导”,用计算属性;想要”值被改时做点额外动作”,用属性观察器。两者职责不同,也可以组合——用计算属性暴露派生信息,用观察器在某个源属性变化时维护别的状态。理解这条界线,结构体就能既灵活又可控。

1-7 计算属性的性能考量

计算属性每次访问都重算。如果计算很贵(比如遍历一个大集合),频繁读它会拖慢程序。这时可考虑把结果缓存起来,或改用存储属性。反过来,如果值容易变、且来源可靠,计算属性省去了手动同步的麻烦。

取舍原则:算得便宜就计算,算得贵就存储(或加缓存)。例如”体积”由三维相乘,计算极便宜,用计算属性正合适;但若是”遍历整棵树统计节点数”,就该考虑缓存结果而非每次现算。

1-8 观察器与计算属性共存

一个结构体里可以同时有存储属性(带观察器)和计算属性(带 get/set)。它们职责互补:观察器盯着”源属性”的变化做联动,计算属性基于源属性向外暴露派生信息。只要理清”谁是源、谁是派生”,两者配合能把数据逻辑写得既清晰又自动。

常见组合是:用一个存储属性当”真相源”,配 willSet/didSet 维护约束;再用若干计算属性暴露各种角度的派生值。这样源只有一份,派生永远最新,改动也集中在一处,利于维护。

1-9 只读计算属性的妙用

只读计算属性特别适合”对外暴露但不可改”的信息。比如一个”圆”结构体,存半径,暴露”面积""周长”为只读计算属性。调用方随时能拿到最新值,却没法乱设面积(面积该由半径决定)。这种”只给看、不给改”的控制,是封装的精髓。

和存储属性相比,计算属性让”派生信息”始终正确;和观察器相比,它不依赖赋值事件,而是每次访问都现算。三种属性机制各司其职,组合使用最妥当。

1-10 练习:分辨属性种类

给你一个结构体,试着判断每个属性该是什么类型:尺寸、面积、创建时间、修改次数。尺寸该是存储属性(源数据);面积该是只读计算属性(由尺寸算);创建时间可以是存储属性带默认值;修改次数可在 willSet/didSet 里统计。多做这种”归类练习”,属性的三种形态就刻进直觉了。

记住总原则:源数据用存储,派生值用计算,需要在赋值前后做事用观察器。三者职责清晰,结构体就既好用又好维护。

1-11 观察器触发的小细节

有一点值得记住:属性观察器在”赋值时”触发,哪怕新值和旧值相同也会触发。这点和某些语言的”变化才通知”不同。如果你的逻辑依赖”确实变了才做事”,需要在 didSet 里自己比对 oldValue(本章前面的 StepCounter 就是这么做的)。知道这个细节,能避免”为什么值没变却执行了”的困惑。

1-12 小结

计算属性不存值,用 getter 现算、setter 反推,只读版可省 get。属性观察器 willSet/didSet 在赋值前后触发,适合约束与联动,但只用于存储属性。两者让结构体的数据既灵活又可控。下章我们用下标给类型加上”方括号取值”的能力。