前言
计算属性是 Swift 中一个很实用得特性,它不存储值,而是提供 getter 和可选的 setter 来间接获取和设置其他属性地值。
基础用法
最常见得用法是根据其他属性来推导出新的值。比如我们经常遇到得文件名拼接:
struct Content {
var name: String
let fileExtension: String
var filename: String {
return name + "." + fileExtension
}
}
let content = Content(name: "iOS开发笔记", fileExtension: "md")
print(content.filename) // 输出:"iOS开发笔记.md"
filename 属性没有存储任何值,每次访问时都会重新计算。可以去掉 return 关键字:
var filename: String {
name + "." + fileExtension
}
读写计算属性
上面的例子是只读的,如果需要设置值,可以添加 setter:
struct ContentViewModel {
privatevar content: Content
init(_ content: Content) {
self.content = content
}
var name: String {
get {
content.name
}
set {
content.name = newValue
}
}
}
// 堆代码 duidaima.com
var content = Content(name: "原始文档", fileExtension: "txt")
var viewModel = ContentViewModel(content)
viewModel.name = "修改后的文档"
print(viewModel.name) // 输出:"修改后的文档"
这样 name 成为了访问内部数据的接口,既可以读取也可以设置。
扩展中使用计算属性
计算属性可以在扩展中为现有类型添加新的属性:
extension CGRect {
var width: CGFloat {
size.width
}
var height: CGFloat {
size.height
}
}
let rect = CGRect(x: 0, y: 0, width: 320, height: 480)
print(rect.width) // 输出:320
重写系统属性
在子类中可以重写只读属性:
final class HomeViewController: UIViewController {
private var shouldHideStatusBar: Bool = true {
didSet {
setNeedsStatusBarAppearanceUpdate()
}
}
override var prefersStatusBarHidden: Bool {
return shouldHideStatusBar
}
}
高级特性
抛出错误
计算属性可以在计算失败时抛出错误:
struct Content {
enum ContentError: Error {
case emptyFileExtension
}
let name: String
let fileExtension: String
var filename: String {
getthrows(ContentError) {
guard !fileExtension.isEmpty else {
throw .emptyFileExtension
}
return name + "." + fileExtension
}
}
}
// 使用时需要加 try
do {
let filename = try content.filename
print(filename)
} catch {
print("文件名生成失败")
}
异步计算属性
对于需要异步操作的场景:
struct FileManager {
var filename: String
var isValid: Bool {
get async throws {
let isValid = await validateFilename(filename)
return isValid
}
}
}
// 使用时需要 await
let isValid = try await fileManager.isValid
使用场景
计算属性适用于以下情况:
1.值依赖其他属性的计算
2.在扩展中为现有类型添加属性
3.作为内部数据的访问接口
需要注意性能问题,计算属性每次访问都会重新执行。看个例子:
struct PeopleViewModel {
let people: [Person]
var oldest: Person? {
people.sorted { $0.age > $1.age }.first
}
}
每次访问 oldest 都会对数组排序,数据量大的时候会影响性能。这种情况建议改为存储属性:
struct PeopleViewModel {
let people: [Person]
let oldest: Person?
init(people: [Person]) {
self.people = people
oldest = people.sorted { $0.age > $1.age }.first
}
}
计算属性 vs 方法
选择计算属性还是方法主要看以下几点:
计算属性适用于:
1.表示对象的某种属性状态
2.不需要外部参数
3.计算逻辑相对简单
方法适用于:
1.需要传入参数
2.有复杂的业务逻辑
3.会产生副作用
struct Rectangle {
var width: Double
var height: Double
// 计算属性:获取面积
var area: Double {
width * height
}
// 方法:缩放操作
func scaled(by factor: Double) -> Rectangle {
Rectangle(width: width * factor, height: height * factor)
}
}
实际开发中的应用
Model 层的组合属性
struct User {
let firstName: String
let lastName: String
var fullName: String {
"\(firstName) \(lastName)"
}
}
UIKit 扩展
extension UIView {
var centerX: CGFloat {
get { center.x }
set { center.x = newValue }
}
// 堆代码 duidaima.com
var centerY: CGFloat {
get { center.y }
set { center.y = newValue }
}
}
状态判断
struct DownloadTask {
var progress: Double
var isCompleted: Bool {
progress >= 1.0
}
}
写在最后
计算属性很实用,但要注意性能开销。复杂计算尽量用存储属性或者方法。另外就是语义要清晰,属性就是属性,动作就是动作,别搞混了。你在项目中用过计算属性吗?遇到过什么坑?评论区聊聊!