Swift 计算属性(Computed Property)详解:原理、性能与实战

原文:What is a Computed Property in Swift? -- SwiftLee

什么是计算属性?

Swift 中的属性分为两大族谱:

类型 描述 存储值
Stored Property(存储属性) 保存一个固定的值,最常见
Computed Property(计算属性) 每次被访问时实时计算出一个值,不占用额外存储

计算属性的核心特征:"算完即走,不落痕迹"。

它通过 getter(必含)和可选的 setter 来间接读取或修改其他属性。

只读计算属性:最常见形态

典型场景:基于已有属性生成新值

swift 复制代码
struct Content {
    var name: String
    let fileExtension: String

    // 计算属性:拼接文件名
    var filename: String {
        name + "." + fileExtension   // 单行可省略 return
    }
}

let content = Content(name: "swiftlee-banner", fileExtension: "png")
print(content.filename)  // swiftlee-banner.png
  • filename 是 只读 的,无法赋值:content.filename = "new.png" 会编译错误。
  • 若显式写 get,代码更冗余,不推荐:
swift 复制代码
var filename: String {
    get { name + "." + fileExtension }
}

可读可写计算属性:暴露私有模型的接口

有时我们想把复杂的模型隐藏在内部,只暴露一个"代理"属性供外部读写------计算属性就能优雅完成。

swift 复制代码
struct ContentViewModel {
    private var content: Content   // 真正的数据模型对外不可见

    init(_ content: Content) {
        self.content = content
    }

    // 计算属性:既读又写,内部转发到 content.name
    var name: String {
        get { content.name }
        set { content.name = newValue }   // newValue 是 Swift 的默认形参
    }
}

var content = Content(name: "swiftlee-banner", fileExtension: "png")
var viewModel = ContentViewModel(content)
viewModel.name = "SwiftLee Post"
print(viewModel.name)  // SwiftLee Post

效果:调用者只知道 name,却不知道内部还有一个复杂的 Content 对象。

在 Extension 中使用计算属性:无痛加功能

计算属性可以写在 extension 里,为现有类型(尤其是系统类型)增加无痛扩展。

swift 复制代码
import UIKit

extension UIView {
    // 快速访问 frame 尺寸
    var width: CGFloat {
        frame.size.width
    }

    var height: CGFloat {
        frame.size.height
    }
}

let view = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 480))
print(view.width)  // 320

优点:无需继承,即刻生效。

在子类中重写计算属性

计算属性还可以 被 override,常用于定制 UIKit 行为。

最简单:直接硬编码

swift 复制代码
final class HomeViewController: UIViewController {
    override var prefersStatusBarHidden: Bool { true }
}

进阶:由内部存储属性驱动

swift 复制代码
final class HomeViewController: UIViewController {
    private var shouldHideStatusBar: Bool = true {
        didSet {
            // 状态改变后刷新系统样式
            setNeedsStatusBarAppearanceUpdate()
        }
    }

    override var prefersStatusBarHidden: Bool { shouldHideStatusBar }
}

何时使用计算属性?官方推荐 3 个条件

条件 示例场景
值依赖其他属性 上文的 filename
在 extension 中定义 UIViewwidth/height
作为内部对象的受控访问点 ContentViewModel.name

个人补充:若计算逻辑 纯静态、且 无状态依赖,考虑直接声明为 static let,避免每次调用重新计算。

性能陷阱:每次访问都会重新计算

计算属性 不会缓存结果,高频访问 + 重计算 = 性能灾难。

反面教材:每次都排序

swift 复制代码
struct PeopleViewModel {
    let people: [Person]

    var oldest: Person? {
        people.sorted { $0.age > $1.age }.first   // O(n log n) 每次都要跑
    }
}

优化:移入初始化器,只算一次

swift 复制代码
struct PeopleViewModel {
    let people: [Person]
    let oldest: Person?

    init(people: [Person]) {
        self.people = people
        oldest = people.max(by: { $0.age < $1.age })   // 或者自己实现一次遍历找最大值
    }
}

经验法则:

  • 数据量小 or 变化频繁 → 计算属性
  • 数据量大 or 代价高昂 → 存储属性 + 预计算

计算属性 VS 方法:如何抉择?

维度 计算属性 方法 (func)
参数 可接受参数
可读性暗示 "轻量级值" "可能耗时"
测试/模拟 不方便 mock 容易 stub/mock
适用场景 简单、无参数、依赖内部状态 复杂、需参数、可能异步或耗时

一句话:重逻辑用方法,轻数据用属性。

总结 & 扩展场景

核心结论

  1. 计算属性 = 无存储 + 实时计算 + 可选 setter。
  2. 带来 语义化 API 与 封装性,但 不缓存。
  3. 在 extension、子类 override、MVVM 视图模型中大放异彩。

扩展实战场景

场景 代码示例 & 注释
格式化输出 var displayPrice: String { "(price)$" }
链式依赖 var isAdult: Bool { age >= 18 }var canDrink: Bool { isAdult }
Core Data 轻量级封装 在 NSManagedObject 的 extension 中,把 primitiveValue包装成计算属性,隐藏 KVC 细节
SwiftUI 绑定 在 ObservableObject 中,用计算属性把 @Published的私有变量暴露为 public 接口
缓存友好型计算属性 结合 lazy或自定义缓存字典,实现 "第一次算,之后读" 的懒加载计算属性
相关推荐
HarderCoder3 小时前
Swift Global Actor 完全指南
swift
HarderCoder3 小时前
Swift Property Wrapper:优雅地消除样板代码
swift
东坡肘子5 小时前
未来将至:人形机器人运动会 | 肘子的 Swift 周报 #099
swiftui·swift·apple
大熊猫侯佩1 天前
反抗军工程师的 “苹果智能” 实战指南:用本机基础模型打造 AI 利刃
ai编程·swift·apple
YungFan2 天前
iOS26适配指南之UIViewController
ios·swift
HarderCoder5 天前
我们真的需要 typealias 吗?——一次 Swift 抽象成本的深度剖析
swift
HarderCoder5 天前
ByAI-Swift 6 全览:一份面向实战开发者的新特性速查手册
swift
HarderCoder5 天前
Swift 中 let 与 var 的真正区别:不仅关乎“可变”与否
swift
HarderCoder5 天前
深入理解 Swift 6.2 并发:从默认隔离到@concurrent 的完整指南
swift