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或自定义缓存字典,实现 "第一次算,之后读" 的懒加载计算属性
相关推荐
蒙小萌19938 小时前
Swift UIKit MVVM + RxSwift Development Rules
开发语言·prompt·swift·rxswift
Antonio91516 小时前
【Swift】Swift基础语法:函数、闭包、枚举、结构体、类与属性
开发语言·swift
Antonio91518 小时前
【Swift】 Swift 基础语法:变量、类型、分支与循环
开发语言·swift
songgeb18 小时前
[WWDC 21]Detect and diagnose memory issues 笔记
性能优化·swift
00后程序员张2 天前
Swift 应用加密工具的全面方案,从源码混淆到 IPA 成品加固的多层安全实践
安全·ios·小程序·uni-app·ssh·iphone·swift
非专业程序员3 天前
Swift 多线程读变量安全吗?
swiftui·swift
Sirius Wu3 天前
开源训练框架:MS-SWIFT详解
开发语言·人工智能·语言模型·开源·aigc·swift
从零开始学习人工智能3 天前
USDT区块链转账 vs SWIFT跨境转账:技术逻辑与场景博弈的深度拆解
开发语言·ssh·swift
RickeyBoy3 天前
Swift6 @retroactive:Swift 的重复协议遵循陷阱
swiftui·swift
东坡肘子6 天前
Homebrew 5.0:并行加速、MCP 加持,与 Intel 的最后倒计时 -- 肘子的 Swift 周报 #0111
rust·swiftui·swift